Tmp: Difference between revisions
No edit summary |
No edit summary |
||
Line 1: | Line 1: | ||
< | ==Overview== | ||
Go has an unique interface system that allows programs to model behavior rather than model types: the behavior is "attached" to types via methods, and, independently, the behavior contract is defined by interfaces. The fact that a type implements an interface is not formally declared via a keyword, like in Java, or otherwise, but is an implicit consequence of a type "having" all the behavior declared by the interface. This is called [[Programming_Languages_Concepts#Duck_Typing|duck typing]]. If a type implements an interface by the virtue of duck typing, a value of the type can be stored in a value of that interface type and the compiler ensure the assignments are correct at compile-time. | |||
:''<span id="duck_typing_does_not_help_readability">Is this a good thing? As far as I can tell, I can't say whether a specific type implements an interface, short of browsing all methods in the package, looking for receivers of that specific type. The compiler does that easily, but I don't. This does not help code readability.</span>'' | |||
A type and its associated [[Go_Concepts_-_The_Type_System#Pointer_Types|pointer type]] may or may not implement the same interface, depending on how the methods are "associated" with the value and pointers of that type. In order to understand how a type or its corresponding pointer type implements an interface, you need to understand [[#Method_Sets|method sets]], which will be explained below. The notion of a type implementing an interface is related to method set inclusion. For more details see "[[Go_Interfaces#When_does_a_Type.2FPointer_Type_Implement_an_Interface.3F|When does a type/pointer type implement an interface?]]" below. | |||
</ | Any used-defined named type ([[Go Structs|structs]], [[Go Type Aliasing|aliased types]]) can be made to implement interfaces. | ||
The fact that Go detaches the contract from implementation with interfaces allows for [[Programming_Languages_Concepts#Polymorphism|polymorphism]]. Wen a method call is made against an interface value, the equivalent method of the stored user-defined type value it is executed. The user-defined type that implements an interface is often called a ''concrete type'' for that interface. | |||
Interfaces in Go tend to be small, exposing only a few functions, or just one function. | |||
==Method Sets== | |||
A type may have a ''method set'' associated with it. | |||
There are two kinds of method sets: [[#Interface_Method_Set|interface method sets]] and [[#User-Defined_Type_Method_Set|user-defined types method sets]]. In the case of user-defined type method set, there's a distinction between the type method set and the pointer type method set, as it will be shown below. | |||
If a type does not have any of the previously mentioned method sets, it is said to have an empty method set. | |||
In a method set, each method must have a unique non-blank method name. | |||
The language specification defines the method sets here: https://golang.org/ref/spec#Method_sets. | |||
===Interface Method Set=== | |||
An ''interface method set'' is a list of method declarations (function name and signature), specified in the [[#Interface_Type|interface type declaration]]. The interface type declaration has the only purpose of defining the method set and giving it a name - the interface name. | |||
The method set only contains method names and signatures, so the interface type does not define ''how'' the behavior is implemented, but just the behavior's contract. The interfaces allow us to hide the incidental details of the implementation. The behavior implementation details are contained by user-defined types, via methods. | |||
Also, the method signatures from the interface method set are independent on whether the corresponding methods from the concrete types are declared with value or pointer receivers. | |||
====Interface Type==== | |||
An ''interface type'' is a user-defined type whose only purpose is to introduce a method set (the [[#Interface_Method_Set|interface method set]]) and to give it a name. | |||
The interface type declaration starts with the <tt>type</tt> keyword, to indicated that this is a user-defined type, followed by the interface name and the keyword <tt>interface</tt>, followed by the interface method set declaration between curly braces. The interface method set declaration consists in a list of method names followed by their [[Go_Concepts_-_Functions#Function_Signature|signatures]]. Unlike in the <tt>struct</tt>'s case, we don't define fields. | |||
<pre> | |||
type MyInterface interface { | |||
<function-name-1><function-signature-1> | |||
<function-name-2><function-signature-2> | |||
... | |||
} | |||
</pre> | |||
Example: | |||
<pre> | |||
type A interface { | |||
m1(i int) int | |||
m2(s string) string | |||
} | |||
</pre> | |||
====Interface Name Convention==== | |||
If the interface type contains only one method, the name of the interface starts with name of the method and ends with the ''er'' suffix. When multiple methods are declared within an interface type, the name of the interface should relate to its general behavior. | |||
====Interface==== | |||
The method set of an interface type it is said to be the type's ''interface''. | |||
===User-Defined Type Method Set=== | |||
====Method Set associated with the Values of a Type==== | |||
The method set associated with the values of a type consists of all methods declared with a ''value receiver'' of that type. | |||
The specification defines ''the method set of a type'' as the method set associated with the values of the type. | |||
====Method Set associated with the Pointers of a Type==== | |||
The method set associated with the pointers of a type <tt>T</tt> consists of both all methods declared with a pointer receiver <tt>*T</tt> ''and'' with a value receiver <tt>T</tt>. | |||
The method set associated with the pointers of a type always includes the method set associated with the values of the type. This is because given a pointer, we can always infer the value pointed by that address, so the methods associated with the value will naturally "work". The reverse is not true - given a value, not always we can get an address for it, and because we are not able to get an pointer, we cannot assume that the methods associated with the pointer will work. [https://github.com/NovaOrdis/playground/tree/master/go/reference This is an example that demonstrates that we cannot always get an address for a value]. | |||
Another way of expressing this is that pointer receiver method are ''less general'' than the value receiver methods: value receiver methods can ''always'' be used, pointer receiver methods can't always be used. | |||
Also see: | |||
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;"> | |||
:[[Go_Concepts_-_Functions#Receivers_and_Interfaces|Receivers and Interfaces]] | |||
</blockquote> | |||
==When does a Type/Pointer Type Implement an Interface?== | |||
Note that notion of a type implementing an interface and its corresponding pointer type implementing the same interface both make sense, and they are subtly different. | |||
According to the language specification, a user-defined type <tt>T</tt> implements an interface if its [[#User-Defined_Type_Method_Set|method set]] (all methods declared with ''a value receiver'' of type <tt>T</tt>) is a superset of the [[#Interface|interface]]. | |||
By extrapolation, a pointer type implements an interface when the method set associated with pointers to that type is a superset of the [[#Interface|interface]]. As per [[Go_Interfaces#User-Defined_Type_Method_Set|User-Defined Type Method Set]] section, the method set associated with the pointer of a type include implicitly the method set associated with the value of the type. | |||
For more details about value and pointer receivers, see: | |||
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;"> | |||
:[[Go_Concepts_-_Functions#Value_and_Pointer_Receivers|Value and Pointer Receivers]] | |||
</blockquote> | |||
For more details about pointer types see: | |||
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;"> | |||
:[[Go_Concepts_-_The_Type_System#Pointer_Types|Pointer Types]] | |||
</blockquote> | |||
==Example== | |||
The example that follows shows how a type <tt>B</tt> implements interface <tt>A</tt>: | |||
<pre> | |||
// | |||
// The interface A declares an interface method set containing a single method m() | |||
// | |||
type A interface { | |||
m() | |||
} | |||
// | |||
// At this point, the type B is not yet linked to the interface A in any way. | |||
// Its method set is empty, so it does not implement interface A, it does not | |||
// implement any interface. | |||
// | |||
type B struct { | |||
i int | |||
} | |||
// | |||
// We make B implement interface A by declaring B as a value receiver for m(). | |||
// This means B's method set includes now m() and it is a superset of A's method | |||
// set. | |||
// | |||
func (b B) m() { | |||
// simply reports the value of i | |||
fmt.Println(b.i) | |||
} | |||
... | |||
// | |||
// B now implements A, so B instances can be assigned to A variables | |||
// | |||
var a A | |||
a = B{1} | |||
a.m() | |||
// | |||
// *B now also implements A, because the method set of the pointer type | |||
// includes the methods declared agains the value so *B instances | |||
// can be assigned to A variables | |||
// | |||
var a2 A | |||
a2 = &B{2} | |||
a2.m() | |||
... | |||
</pre> | |||
===A Variation of the Example=== | |||
Note that if in the example above we declare | |||
<pre> | |||
func (b *B) m() { | |||
... | |||
} | |||
</pre> | |||
instead of | |||
<pre> | |||
func (b B) m() { | |||
... | |||
} | |||
</pre> | |||
then the <tt>a = B{1}</tt> assignment would fail to compile with: | |||
<pre> | |||
./main.go:23: cannot use B literal (type B) as type A in assignment: | |||
B does not implement A (m method has pointer receiver) | |||
</pre> | |||
This is because the type B does not implement the interface, because m() is not in the type B method set. | |||
However, if we try: | |||
<pre> | |||
var a A | |||
a = &B{1} | |||
a.m() | |||
</pre> | |||
it works in both cases, because the method set associated with the B pointers include both <tt>func (b B) m() ...</tt> and <tt>func (b *B) m() ...</tt> so the pointer type B implements the interface. | |||
==Passing Interfaces to Functions== | |||
Interfaces can be used as arguments to functions. Passing an interface instance insures that the function body can rely on the fact the interface methods are available on the passed instance. | |||
With the example above, we declare a function f() that expects an interface A and we pass a B instance when invoking the function: | |||
<pre> | |||
... | |||
type A interface { | |||
m() | |||
} | |||
func (b B) m() { | |||
... | |||
} | |||
func f(a A) { | |||
a.m() | |||
} | |||
... | |||
b := B{1} | |||
f(b) | |||
... | |||
</pre> | |||
Note that the B instance can be passed by value (as in the example above) or by reference (as in the example below). Both cases work for reasons explained in the [[#Method_Set_associated_with_the_Pointers_of_a_Type|Method Set associated with the Pointers of a Type]] section: | |||
<pre> | |||
... | |||
b := B{1} | |||
f(&b) | |||
... | |||
</pre> | |||
or (same thing) | |||
<pre> | |||
... | |||
bPtr := new(B) | |||
f(bPtr) | |||
... | |||
</pre> | |||
==Interfaces as Fields== | |||
Interfaces can be used as [[Go_Structs#Fields|fields]] in <tt>[[Go_Structs|struct]]</tt>s. | |||
==Implementation Details== | |||
An interface variable is a two-word data structure. | |||
The first word contains a pointer to an internal table called ''iTable'', which contains type information about the stored value: the concrete type of the value that has been stored and a list of methods associated with the value. The second word is a reference to the stored value. | |||
The combination of type information and pointer binds the relationship between the two values. | |||
DEPLETE: [[Go Concepts - The Type System]] |
Revision as of 21:28, 13 August 2024
Overview
Go has an unique interface system that allows programs to model behavior rather than model types: the behavior is "attached" to types via methods, and, independently, the behavior contract is defined by interfaces. The fact that a type implements an interface is not formally declared via a keyword, like in Java, or otherwise, but is an implicit consequence of a type "having" all the behavior declared by the interface. This is called duck typing. If a type implements an interface by the virtue of duck typing, a value of the type can be stored in a value of that interface type and the compiler ensure the assignments are correct at compile-time.
- Is this a good thing? As far as I can tell, I can't say whether a specific type implements an interface, short of browsing all methods in the package, looking for receivers of that specific type. The compiler does that easily, but I don't. This does not help code readability.
A type and its associated pointer type may or may not implement the same interface, depending on how the methods are "associated" with the value and pointers of that type. In order to understand how a type or its corresponding pointer type implements an interface, you need to understand method sets, which will be explained below. The notion of a type implementing an interface is related to method set inclusion. For more details see "When does a type/pointer type implement an interface?" below.
Any used-defined named type (structs, aliased types) can be made to implement interfaces.
The fact that Go detaches the contract from implementation with interfaces allows for polymorphism. Wen a method call is made against an interface value, the equivalent method of the stored user-defined type value it is executed. The user-defined type that implements an interface is often called a concrete type for that interface.
Interfaces in Go tend to be small, exposing only a few functions, or just one function.
Method Sets
A type may have a method set associated with it.
There are two kinds of method sets: interface method sets and user-defined types method sets. In the case of user-defined type method set, there's a distinction between the type method set and the pointer type method set, as it will be shown below.
If a type does not have any of the previously mentioned method sets, it is said to have an empty method set.
In a method set, each method must have a unique non-blank method name.
The language specification defines the method sets here: https://golang.org/ref/spec#Method_sets.
Interface Method Set
An interface method set is a list of method declarations (function name and signature), specified in the interface type declaration. The interface type declaration has the only purpose of defining the method set and giving it a name - the interface name.
The method set only contains method names and signatures, so the interface type does not define how the behavior is implemented, but just the behavior's contract. The interfaces allow us to hide the incidental details of the implementation. The behavior implementation details are contained by user-defined types, via methods.
Also, the method signatures from the interface method set are independent on whether the corresponding methods from the concrete types are declared with value or pointer receivers.
Interface Type
An interface type is a user-defined type whose only purpose is to introduce a method set (the interface method set) and to give it a name.
The interface type declaration starts with the type keyword, to indicated that this is a user-defined type, followed by the interface name and the keyword interface, followed by the interface method set declaration between curly braces. The interface method set declaration consists in a list of method names followed by their signatures. Unlike in the struct's case, we don't define fields.
type MyInterface interface { <function-name-1><function-signature-1> <function-name-2><function-signature-2> ... }
Example:
type A interface { m1(i int) int m2(s string) string }
Interface Name Convention
If the interface type contains only one method, the name of the interface starts with name of the method and ends with the er suffix. When multiple methods are declared within an interface type, the name of the interface should relate to its general behavior.
Interface
The method set of an interface type it is said to be the type's interface.
User-Defined Type Method Set
Method Set associated with the Values of a Type
The method set associated with the values of a type consists of all methods declared with a value receiver of that type.
The specification defines the method set of a type as the method set associated with the values of the type.
Method Set associated with the Pointers of a Type
The method set associated with the pointers of a type T consists of both all methods declared with a pointer receiver *T and with a value receiver T.
The method set associated with the pointers of a type always includes the method set associated with the values of the type. This is because given a pointer, we can always infer the value pointed by that address, so the methods associated with the value will naturally "work". The reverse is not true - given a value, not always we can get an address for it, and because we are not able to get an pointer, we cannot assume that the methods associated with the pointer will work. This is an example that demonstrates that we cannot always get an address for a value.
Another way of expressing this is that pointer receiver method are less general than the value receiver methods: value receiver methods can always be used, pointer receiver methods can't always be used.
Also see:
When does a Type/Pointer Type Implement an Interface?
Note that notion of a type implementing an interface and its corresponding pointer type implementing the same interface both make sense, and they are subtly different.
According to the language specification, a user-defined type T implements an interface if its method set (all methods declared with a value receiver of type T) is a superset of the interface.
By extrapolation, a pointer type implements an interface when the method set associated with pointers to that type is a superset of the interface. As per User-Defined Type Method Set section, the method set associated with the pointer of a type include implicitly the method set associated with the value of the type.
For more details about value and pointer receivers, see:
For more details about pointer types see:
Example
The example that follows shows how a type B implements interface A:
// // The interface A declares an interface method set containing a single method m() // type A interface { m() } // // At this point, the type B is not yet linked to the interface A in any way. // Its method set is empty, so it does not implement interface A, it does not // implement any interface. // type B struct { i int } // // We make B implement interface A by declaring B as a value receiver for m(). // This means B's method set includes now m() and it is a superset of A's method // set. // func (b B) m() { // simply reports the value of i fmt.Println(b.i) } ... // // B now implements A, so B instances can be assigned to A variables // var a A a = B{1} a.m() // // *B now also implements A, because the method set of the pointer type // includes the methods declared agains the value so *B instances // can be assigned to A variables // var a2 A a2 = &B{2} a2.m() ...
A Variation of the Example
Note that if in the example above we declare
func (b *B) m() { ... }
instead of
func (b B) m() { ... }
then the a = B{1} assignment would fail to compile with:
./main.go:23: cannot use B literal (type B) as type A in assignment: B does not implement A (m method has pointer receiver)
This is because the type B does not implement the interface, because m() is not in the type B method set.
However, if we try:
var a A a = &B{1} a.m()
it works in both cases, because the method set associated with the B pointers include both func (b B) m() ... and func (b *B) m() ... so the pointer type B implements the interface.
Passing Interfaces to Functions
Interfaces can be used as arguments to functions. Passing an interface instance insures that the function body can rely on the fact the interface methods are available on the passed instance.
With the example above, we declare a function f() that expects an interface A and we pass a B instance when invoking the function:
... type A interface { m() } func (b B) m() { ... } func f(a A) { a.m() } ... b := B{1} f(b) ...
Note that the B instance can be passed by value (as in the example above) or by reference (as in the example below). Both cases work for reasons explained in the Method Set associated with the Pointers of a Type section:
... b := B{1} f(&b) ...
or (same thing)
... bPtr := new(B) f(bPtr) ...
Interfaces as Fields
Interfaces can be used as fields in structs.
Implementation Details
An interface variable is a two-word data structure.
The first word contains a pointer to an internal table called iTable, which contains type information about the stored value: the concrete type of the value that has been stored and a list of methods associated with the value. The second word is a reference to the stored value.
The combination of type information and pointer binds the relationship between the two values.
DEPLETE: Go Concepts - The Type System