The meaning of Go’s interfaces has changed since Go 1.18, and there are three new concepts related to Go interfaces that many people are not aware of: type set
, specific type
and structural type
.
type set
The type set is called a type set and is a new concept added to Go 1.18 for those who follow Go generics.
Unlike Java, which requires a class to be defined explicitly to implement an interface, Go does not require this. In Go, as long as a type implements all the methods defined by an interface, it implements that interface and can be assigned to variables of that interface type, or as real or return values of methods of that interface type, a design sometimes referred to as `duck typing’. As long as it walks like a duck and quacks like a duck, then it is a duck, which is a classic description of duck typing.
In Go 1.18, interfaces no longer represent a collection of methods, but a collection of types (type set). Whenever a type is in the type set of an interface, then we say that this type implements the interface. If the interface is used as a type constraint, then any element in the type set defined by the interface can instantiate the type parameter.
So, in effect, the Go language specification has had to redefine the meaning of interfaces in order to support the extension of interfaces as type constraints, which is why type collections have emerged.
In fact, the concept of an interface’s method set is also in The method set of an interface is the intersection of the set of methods of all elements in the set of types of that interface.
rThis article assumes that you have some knowledge of Go generics. If you don’t, you must know that Go 1.18 supports the inclusion of type elements in addition to the original method elements, which can be a type T
, or an approximate type ~T
, or a union of them int|int8|int16|int32|int64|~string
.
If an interface I
is embedded in another interface E
, then the type set of I
is the intersection of the set of types it displays and the set of types of the embedded interface E
. This is equivalent to E
narrowing the type set of interface I
.
How do you determine the type set of an interface? Follow these principles:
- The type set of an empty interface
any
,interface{}
is the set of all types So things likeint
,string
,strcut{}
,MyStruct
,func foobar()
,chan int
,map[int]string
,[]int
and so on are in the set of types of the empty interface - The set of types of a non-empty interface is the intersection of the set of types of the interface elements
So what is the set of types of interface elements? See the following four articles.
We have already mentioned that interface elements contain type elements and method elements.
- A method’s type set is the set of all types that define the method, i.e. as long as the set of methods of a certain type contains the method, then it belongs to the method’s type set
For example, if an interface has a method such as
String() string
, then all types that implement the method belong to the set of types defined byString() string
, e.g.net.IP
. - A collection of types that is not an interface type is a collection of types that contains only that type
For example, a type collection of
int
contains only one element likeint
. - The set of types of a close element
~T
is the set of all types whose underlying type isT
e.g.MyInt
intype MyInt int
is the set of types of~int
- The set of types of the union element
t1|t2|...|tn
is the merge set of the set of types of these union elements
- A method’s type set is the set of all types that define the method, i.e. as long as the set of methods of a certain type contains the method, then it belongs to the method’s type set
For example, if an interface has a method such as
The following examples enumerate sets of types.
|
|
specific type and specific type set
Another important concept of an interface is specific type
.
Only interfaces containing a type element define a specific type (which may be an empty type).
If not strictly speaking, specific types are those types that appear in the type elements defined in T
, ~T
, t1|t2|... |tn
in t1
, t2
, ...
, tn
.
More precisely, for a given interface I
, the set of specific types corresponds to the set of types represented by that interface 𝑅, where 𝑅 is required to be non-empty and finite. Otherwise, if 𝑅 is empty or infinite, the interface has no specific type.
For a given interface, type element or type, the set of types it represents 𝑅 is defined as follows.
- For an interface without any type elements, its 𝑅 is all elements (infinite). So it does not have a specific type.
- If an interface has a type element, its 𝑅 is the intersection of the types represented by its elements Whether or not it has a specific type depends on whether 𝑅 is non-empty and finite.
- For a non-interface type
T
, or~T
, its 𝑅 is the set containing the typeT
- For a union element
t1|t2|...|tn
, its 𝑅 is the merge set of the types represented by these items
Here is an example of a particular type:
|
|
type set vs specific type set
There is a difference between a type set and a specific type set, as can be seen from their definitions above.
An interface may have a specific type set that is not empty, even if the type is empty.
For example, interface{ int; m() }
, its type set is empty (int does not implement the m method), but its specific type is int
.
An interface may have an infinite set of types even if it has a finite number of specific types
For example, interface{ ~int; m() }
, its specific type is int, but its set of types is infinite (any type that has an underlying int and implements method m is part of its set of types).
So what is the point of defining a specific type?
Application of specific types
Specific types are used to determine whether a type argument supports indexing, such as a[x]
.
For example, an expression a[x]
, an instance of a
could be of type array, pointer to an array, slice, string, map.
If the type of a
is that of the type parameter P
, under what conditions will our code a[x]
not compile with an error?
The required conditions would be related to the specific type.
- P must be of a particular type
- the indexing of
a[x]
is supported for the value a of a particular type of P - All specific types of P must be the same. In this case, the element type of the string type is byte (https://github.com/golang/go/issues/49551)
- If the specific type of P contains the type map, then all of its specific types must be map, and all of its keys must be of the same type
So sometimes if you define a joint element interface with map, slice, string, you can’t use the
a[x]
index type for instances of this interface, the elements are all of type int a[x]
is an element of array, slice, string with index x, or an element of type map with key x. The type of a must be the same- if the specific type of P contains the string type, then
a[x]
cannot be assigned to it (strings are immutable)
The specific type is also used on the type conversion definition.
For a variable x
, if its type is V
and the type to be converted is T
, x can be converted to type T
provided that the following clause is satisfied:
- every value of a particular type of
V
can be converted to every particular type ofT
- only if
V
is a type parameter andT
is not, then every value of a particular type ofV
can be converted toT
- only if
T
is a type parameter,x
can be converted to every specific type of T
In a nutshell, every specific type is satisfied if it is a type parameter, and this type is satisfied if it is not.
Also, for type arguments to call the built-in functions len
, cap
, their specific types must be required to allow the use of these built-in functions.
structural type
For an interface T
to be structural, one of the following conditions must be satisfied:
- there exists a single type
U
, which is the same underlying type as every element in the type set ofT
- the set of types of
T
contains only chan types, and their element types are allE
, and all chans have the same orientation (not necessarily identical)
A structured type contains a structure type which, depending on the conditions above, may be:
- type
U
, or - if
T
contains only bidirectional chan, the structure type ischan E
, otherwise it may bechan<- E
or<-chan E
The following is a structured interface containing structure types:
|
|
The Go language specification doesn’t have much more to say about the structured interface and how to use it, it’s more about it getting the underlying structural type internally and doing type checking, like the example below which throws a no structural type
compiler error:
|
|
The following code also throws a M has no structural type
compiler error
For example, the following mistake is often made when using Go generics, although []byte
, map[int]byte
and string
can all range, and the key(index) and value types are the same, it also throws the error R has no structural type
: