Map types

Map types A map is an unordered group of elements of one type, called the element type, indexed by a set of unique keys of another type, called the key type. The value of an uninitialized map is nil. MapType = "map" "[" KeyType "]" ElementType . KeyType = Type . Maps should be familiar to just about anyone who’s familiar with programming, by one name or another. “Hashmaps,” “dictionaries,” “associative arrays,” or even simply “objects” in JavaScript or JSON.


Implementing an interface

It feels like it’s been a month since we started on interfaces. Today we’re covering the final section on this topic! Implementing an interface A type T implements an interface I if T is not an interface and is an element of the type set of I; or T is an interface and the type set of T is a subset of the type set of I. A value of type T implements an interface if T implements the interface.


Recursive interfaces

Check out this week’s episode of the Cup o’ Go podcast: What the ʕ◔ϖ◔ʔ? New merch, TDD book interview with Adelina Simion, and more You can probably guess Go’s rules about recursively embedding interfaces. In short: It’s not allowed. General interfaces … An interface type T may not embed a type element that is, contains, or embeds T, directly or indirectly. // illegal: Bad may not embed itself type Bad interface { Bad } // illegal: Bad1 may not embed itself using Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 } // illegal: Bad3 may not embed a union containing Bad3 type Bad3 interface { ~int | ~string | Bad3 } // illegal: Bad4 may not embed an array containing Bad4 as element type type Bad4 interface { [10]Bad4 } Note however, that, an interface method may refer to the interface itself.


Interfaces as type constraints

If you’ve been playing around with interface types while reading about them for the last couple of weeks, you have probably noticed something: Many of the interface types described don’t work as variables. Here we see why: General interfaces … Interfaces that are not basic may only be used as type constraints, or as elements of other interfaces used as constraints. They cannot be the types of values or variables, or components of other, non-interface types.

Subscribe to Boldly Go: Daily

Every day I'll send you advice to improve your understanding of Go. Don't miss out! I will respect your inbox, and honor my privacy policy.

Unsure? Browse the archive.


Union restrictions

General interfaces … Implementation restriction: A union (with more than one term) cannot contain the predeclared identifier comparable or interfaces that specify methods, or embed comparable or interfaces that specify methods. Or putting this another way: Unions (that is, interface type parameters with a | character), only work on interfaces defined by other type parameters. interface { int | float32 // Valid int | error // Invalid: error is an interface that specifies a method int | comparable // Invalid: comparable is not a type parameter-defined interface } Quotes from _The Go Programming Language Specification_ Version of December 15, 2022


Type parameter restrictions

We’ll get to a full discussion of type parameters later on, but knowing at least what they look like is useful for the next section. So here’s an example, used in the definition of a generic function: func min[T ~int|~float64](x, y T) T { In this example, min[T ~int|~float64] represents the type parameter. With that in mind… General interfaces … The type T in a term of the form T or ~T cannot be a type parameter, and the type sets of all non-interface terms must be pairwise disjoint (the pairwise intersection of the type sets must be empty).


Unions of type sets

General interfaces … Union elements denote unions of type sets: // The Float interface represents all floating-point types // (including any named types whose underlying types are // either float32 or float64). type Float interface { ~float32 | ~float64 } Here we see our first example of a union of type sets. The example shows an interface of all types with an underlying float type. The equivalent for integer types is a bit longer, due to the large number of distinct integer types in Go, but would look like:


Restrictions on underlying type terms

General interfaces … In a term of the form ~T, the underlying type of T must be itself, and T cannot be an interface. type MyInt int interface { ~[]byte // the underlying type of []byte is itself ~MyInt // illegal: the underlying type of MyInt is not MyInt ~error // illegal: error is an interface } Once again, the spec examples are pretty well explained. TL;DR; the ~ prefix must always be associated with an underlying data type.


Interface examples with type elements

Let’s look at some interface examples from the spec: General interfaces … // An interface representing only the type int. interface { int } // An interface representing all types with underlying type int. interface { ~int } // An interface representing all types with underlying type int that implement the String method. interface { ~int String() string } // An interface representing an empty type set: there is no type that is both an int and a string.


Interfaces don't contain interfaces

As we learned recently, Go interface elements may contain a type term of a non-interface type. It’s worth re-iterating that these are non-interface types. In particular, as the spec states: General interfaces … By construction, an interface’s type set never contains an interface type. That is to say, that the following is invalid: type interface foo { /* some interface elements */ } type interface bar { foo } Actually, I lied.


"Duck Typing" defined

I’ve already introduced the concept of duck typing, but now we have more or less a formal definition: General interfaces … The quantification “the set of all non-interface types” refers not just to all (non-interface) types declared in the program at hand, but all possible types in all possible programs, and hence is infinite. Similarly, given the set of all non-interface types that implement a particular method, the intersection of the method sets of those types will contain exactly that method, even if all types in the program at hand always pair that method with another method.