Go is a strongly typed, static programming language. Almost every line of code we program in Go is inextricably linked to a type. Therefore, to learn Go in depth, we must first have a comprehensive and in-depth understanding of the Go type system, which gives us a holistic view of the specific type-related aspects of the Go language.
1. What is the type system
As a Gopher with some Go programming experience, you have some knowledge of types in the Go language, for example: Go has built-in native integer types, floating point types, complex types, string types, function types, and provides composite types such as arrays, slices, maps, structs, channels, and interface types that represent behavioural abstractions. With the type keyword provided by Go, we can also customise types and much more.
So have you ever thought about the question: why are there types? What benefits can types bring? Looking back at the history of programming languages (see below), we see that types are an important abstraction that distinguishes high-level languages from machine languages and low-level languages.
From the machine’s point of view, data of any type is 0101 binary data, but it is very difficult and inefficient for programmers to code directly in machine language; assembly language takes the hierarchy up to multi-byte data-oriented coding, where the operands of assembly instructions are fixed-length bytes, e.g. movb operates on one byte, movl operates on four bytes. Assembly instructions don’t care what data is actually stored, they just move a specific length of data between addresses. Clearly the level of abstraction in assembly is still not high and it is still very difficult and inefficient to write programs directly in sinks.
High-level languages are advanced because they create the important abstraction of types, which shields the developer from complex representations of machine-level data. The complex byte and bit manipulation beneath the type is facilitated by the compiler and runtime of the high-level language, and the developer only needs to code type-oriented, meaning that the type becomes the ‘interface’ between the developer and the compiler.
Type-oriented programming requires the developer to understand the capabilities of types, the abstractions they represent and to follow the rules/constraints on the use of types. The type determines the range of values you can store in instances of the type; the type determines the operations you can perform on the type; the type determines the storage space required for variables of the type; the type determines the method of making connections to other types: combination, ‘inheritance’ or interface implementation, etc.
So who gives these abilities, rules and constraints to types? Yes, it is the type system of the programming language!
The type system is at the heart of a high-level language. It is found in the language specification, which specifies the capabilities, rules and constraints of types to the developer; it is found in the compiler, which ensures that the developer uses the types correctly and it is also found in the language runtime, which provides dynamic capabilities for types such as polymorphism.
It is fair to say that high-level programming languages use type systems to empower types and manage them. However, the design and implementation of the type system varies considerably from language to language, so what makes the Go language’s type system different? Let’s take a closer look at Go’s type system.
2. Go’s type system
Below we illustrate the capabilities and shortcomings of the Go type system in terms of type definition, type inference, type checking, type combination, and so on.
2.1. Type definitions
As you know Go supports almost all types, here is a screenshot of the list of type categories in the Go spec.
Go also supports custom types defined using the type keyword and type alias.
A custom type and its underlying type are two completely different types, and a type alias does not introduce a new type and is equivalent to the original type.
However there are two types common in other languages that the Go type system does not support, one is the union union type, in which all its fields share the same memory space.
The other type is the enum enum type. However enum types can be emulated to some extent with const (optionally with iota).
Go supports generics from version 1.18 onwards, giving the Go type system the ability to define types and functions with type parameters.
2.2. Type inference
The Go type system supports automatic type inference capabilities, where the compiler can infer the type of a variable or function without us having to explicitly specify it.
In addition to support for normal type inference, Go also supports inference on autotyped arguments for generic types, here is an example from the go spec.
In the example, the compiler can automatically infer that the type parameter Number for scale is of type float64 by the type of the argument passed in during the scale call.
2.3. Type checking
Go is a strongly typed static programming language, meaning that every variable must have its type declared before it can be used. Once we have a type, we can manipulate it according to the valid operations specified by the Go type system for that type.
The Go compiler and runtime checks variable types during compilation and at runtime respectively, to ensure that operations are only used on the correct type and that the rules of the type system are followed by the program, to ensure type safety etc.
Go is a strongly typed language and has no implicit type conversions, all type conversions are implemented as explicitly intended explicit type conversions, the Go compiler checks for type conversions during compilation and only the two types that are underlying type compatible can be explicitly transformed.
In addition to static checks during compilation, the Go type system also supports dynamic type checking at runtime, for example: checking that the type instance passed to an interface variable implements that interface; checking index bounds of array and slice types at runtime to ensure that indexes do not cross bounds, ensuring memory safety, etc.
However Go also provides means of bypassing type system checks, such as unsafe.Pointer and reflection.
2.4. Type Combination
Go is not a classical OO language, its types can have their own methods, but Go does not provide the complex inheritance hierarchy of classical OO, there are no parents, no subclasses and no constructors for type initialisation. In Go’s type system, the only way to create combinations between types is to combine them, and through type embedding we can implement various combinations, either by embedding non-interface types or by embedding interfaces to define the new combined types.
Type combination allows us to combine various types together to collectively provide aggregated behaviour to the outside world, including polymorphic capabilities.
The standard polymorphic capabilities in Go are implemented by the interface type, and methods are dispatched at runtime depending on the specific type passed to the interface type variable. For example, the Quack in the AnimalQuackInForest example below is dispatched depending on the specific type instance passed in, and is dispatched to
Bird.Quack in turn.
The implementation relationship between the type and the interface is implicit, the type does not need to be explicitly informed of the type of interface to be implemented using the implements keyword.
Functions in Go are first class citizens, and function types can also exhibit some runtime polymorphism, with the final execution of an instance of a function type depending on the value of the function object passed in at runtime.
Go provides a powerful and interesting type system, although Go does not provide enum, union types, nor does it support operator overloading, function overloading, structured error handling, and optional/default function arguments. This is not unrelated to the decision made by Go’s designers to keep Go simple. The type system is also instrumental in keeping Go a safe language.
If you are serious about Go programming, you should invest time in understanding its type system and its peculiarities, which will be well worth your time.