Overview Of Go Type System

This article will introduce all kinds of types in Go. All kinds of concepts in Go type system will also be introduced. Without knowing these concepts, it is hard to have a thorough understanding of Go.

Concept: Basic Types

Built-in basic types in Go have been introduced in built-in basic types and basic value literals. For completeness of the current article, these buint-in basic types are re-listed here.

Except string types, Go 101 article series will not try to explain more on other basic types.

Concept: Composite Types

Go supports following composite types:
Unlike basic types, composite types may be denoted as their respective type literals. Following are some literal representation examples of all kinds of composite types.
// Assume T is an arbitrary type and Tkey is
// a type supporting comparison (== and !=).

*T         // a pointer type
[5]T       // an array type
[]T        // a slice type
map[Tkey]T // a map type

// a struct type
struct {
	name string
	age  int
}

// a function type
func(int) (bool, string)

// an interface type
interface {
	Method0(string) int
	Method1() (int, bool)
}

// some channel types
chan T
chan<- T
<-chan T

(Below will explain which types don't support comparisons.)

Fact: Kinds Of Types

Each of the above mentioned basic and composite types corresponds to one kind of types. Besides these type kinds. The unsafe pointer types introduced in the unsafe standard package also belong to one kind of types in Go. So, up to now (Go 1.11), Go has 26 kinds of types.

Syntax: Type Definitions

(Type definition, or type definition declaration, is called type declaration before Go 1.9. Since Go 1.9, type definition has become one of two kinds of type declartions in Go.)

In Go, we can define new types by using the following syntaxes. In the following example, type is a keyword.
// Define a solo new type.
type NewTypeName SourceType

// Define multiple new types together.
type (
	NewTypeName1 SourceType1
	NewTypeName2 SourceType2
)

New type names must be identifiers.

Note,
  • a new defined type and its respective source type in type definitions are two distinct types.
  • two different defined types are always two distinct types.
  • the new defined type and the source type will share the same underlying type (see below for what are underlying types), and their values can be converted to each other.
  • types can be defined within function bodies.
Some type definition examples:
// The following new defined and source types are all basic types.
type (
	MyInt int
	Age   int
	Text  string
)

// The following new defined and source types are all composite types.
type IntPtr *int
type Book struct{author, title string; pages int}
type Convert func(in0 int, in1 bool)(out0 int, out1 string)
type StringArray [5]string
type StringSlice []string

func f() {
	// The three defined types can be only used within the function.
	type PersonAge map[string]int
	type MessageQueue chan string
	type Reader interface{Read([]byte) int}
}

Syntax: Type Alias Declarations

(Type alias declaration is one new kind of type declartions added since Go 1.9.)

As above mentioned, there are only two built-in type aliases in Go, byte (alias of uint8) and rune (alias of int32). They are the only two type aliases before Go 1.9.

Since Go 1.9, we can declare custom type alias by using the following syntaxes. The syntax of alias declaration is much like type definition, but please note there is a = in each type alias declaration.
type (
	Name = string
	Age  = int
)

type table = map[string]int
type Table = map[Name]Age

Type alias names must be identifers. Like type definitions, type aliases can also be declared within function bodies.

By the above declarations, Name is an alias of string, they denote the same type. The same relation is for the other three pairs of type:
  • Age and int
  • table and map[string]int
  • Table and map[Name]Age

In fact, type map[string]int and map[Name]Age also denote the same type. So the four types involved in the last two type alias declarations all denote the same type.

Note, although table and Table denote the same type, Table is an exported type so it can be used by other packages but table can't.

Concept: Named Types vs. Unnamed Types

In Go,

An unnamed type must be a composite type, It is not true vice versa, for a composite type may be a defined type or an alias type.

Concept: Defined Types vs. Non-Defined Types

Before Go 1.9, there is only one kind of type declaration. It is the current type definition. So it is very clear that a named type must be a defined type, and defined type and named type are almost the same concept before Go 1.9. However, since Go 1.9, things become a little complicated by the introduction of type alias. Now a named type may also be a type alias and an alias type may be a defined type or not.

To make many explanations easy and clear, Go 101 adopts a new terminology, non-defined type, to represent unnamed types and alias of unnamed types. This terminology doesn't show up in Go specification.

All basic types are defined. A non-defined type must be a composite type.

In the following example. alias type C and type literal []string are both non-defined types, but type A and alias type B are both defined types.
type A []string
type B = A
type C = []string

Concept: Underlying Types

In Go, each type has an underlying type. Rules: Examples:
// The underlying types of the following ones are both int.
type (
	MyInt int
	Age   MyInt
)

// The following new types have different underlying types.
type (
	IntSlice   []int   // underlying type is []int
	MyIntSlice []MyInt // underlying type is []MyInt
	AgeSlice   []Age   // underlying type is []Age
)

// The underlying types of Ages and AgeSlice are both []Age.
type Ages AgeSlice

How to trace to the underlying type for a given user defined type? The rule is, when a built-in basic type, unsafe.Pointer or an unnamed type is met, the tracing will be stopped. Take the type declarations above as examples, let's trace their underlying types.

MyInt → int
Age → MyInt → int
IntSlice → []int
MyIntSlice → []MyInt []int
AgeSlice → []Age []MyInt []int
Ages → AgeSlice → []Age []MyInt []int

In Go,

The concept of underlying type plays an important role in value conversions, assignments and comparisions in Go.

Concept: Values

An instance of a type is called a value, of the type. A type may have many values, but it has only one zero value. Values of the same type share some common properties.

Each type has a zero value, which can be viewed as the default value of the type. The predeclared nil identifier can used to represent as zero values of slice, map, function, channel, pointer (including type-unsafe pointer) and interface types. About more on nil, please read nil in Go later.

There are several kinds of value representation forms in code, including literals, named constants, variables and expressions, though the former three can be viewed as special cases of the latter one.

Value may be typed or untyped. Except the predeclared nil, values of composite types are all typed. Untyped values are all basic values or the predeclared nil.

All kinds of basic value literals have been introduced in in the article basic types and basic value literals. There are two more kinds of literals in Go, composite literals and function literals.

Function literals, as the name implies, are used to represent function values. A function declaration is composed of a function literal and a function identifier. In other words, a function declaration only has one more part, the function identifier part, than its corresponding function literal part. An annoymous function is only composed of a function literal. Function literals will be formally introduced in the article functions in Go.

Composite literals are used to represent values of struct types and container types (arrays, slices and maps), Please read structs in Go and containers in Go for details.

There are no literals to represent values of pointer, channel and interface types.

Concept: Value Parts

At run time, many values are stored somewhere in memory. In Go, each of such values has a direct part, however, some of them each has one or more indirect parts. Each value part occupies a continuous memory segment. The indirect underlying parts of a value are referenced by its direct part through pointers.

The terminology value part is not defined in Go specification. It is just used in Go 101 to make some explanations simpler and help Go programmers understand Go types and values better. Please read the article value parts for more information on value parts.

Concept: Value Sizes

When a value is stored in memory, the number of bytes occupied by the direct part of the value is called the size of the value. All values of the same type have the same value size, so the size of values of a type is often called as the size, of value size, of the type. We can use the Sizeof function in the unsafe standard package to get the size of any value.

Go specification doesn't specify values size requirements for non-numeric types. The requirements for value sizes of all kinds of basic numeric types are listed in the article basic Types and basic value literals.

Concept: Dynamic Type And Dynamic Value Of An Interface Value

Interface values are the values whose types are interface types.

Each interface value can box a non-interface value in it. The value boxed in an interface value is called the dynamic value of the interface value. The type of the dynamic value is called the dynamic type of the interface value. An interface value boxing nothing is a nil interface value.

For more about interface types and values, please read this article.

Concept: Signature Of Function Types

The signature of a function type is composed of the input parameter definition list and the output result definition list of the function.

The function name and body are not parts of a function signature. Parameter and result types are important for a function signature, but parameter and result names are not important.

Please read functions in Go for more details about function type and function values.

Concept: Method And Method Set Of A Type

In Go, some types can have methods. Methods can also be called member functions.

The method set of a type is composed of all the methods of the type. If the method set of a type is the super set of the method set of an interface type, we say the type implements the interface type.

Concept: Fields Of A Struct Type

A struct type is composed of a collection of member variables. Each of the member variables is called a field of the struct type. For example, the following struct type Book has three fields, author, title and pages.
struct {
	author string
	title  string
	pages  int
}

For more about struct types and values, please read structs in Go.

In Go, we can extend a type which satisfies some conditions by embedding this type in a struct type.

Concept: Base Type Of A Pointer Type

For a pointer type, assume its underlying type can be denoted as *T in literal, then T is called the base type of the pointer type.

For more about pointer types and values, please read pointers in Go.

Concept: Container Types

Array, slice and map can be viewed as formal built-in container types.

Informally, string and channel types can also be viewed as container types.

Each value of a container type has a length, either the container type is a formal one or an informal one.

For more about container types and values, please read containers in Go.

Concept: Key Type Of A Map Type

If the underlying type of a map type can be denoted as map[Tkey]T, then Tkey is called the key type of the map type. Tkey must be a comparable type.

Concept: Element Type Of A Container Type

The types of the elements stored in a container value must be identical. The identicial type of the elements is called the element type of the container type of the container value.

Concept: Directions Of Channel Types

Channel values can be viewed as synchronized first-in-first-out (FIFO) queues. Channel types and values have directions.

For more about channel types and values, please read channels in Go.

Fact: Types Which Support Or Don't Support Comparisons

Currently (Go 1.11), following types don't support comparisons (with the == and != operators):

Above listed types are called uncomparable types. Compilers forbid comparing two values of uncomparable types.

Note, some go tools may call uncomparable types as incomparable types.

All basic types, pointer types, channel types and interface types are comparable. However, comparing two interface values may panic at run time if both the dynamic types of them are the same uncomparable type.

Values of uncomparable types are called uncomparable values. Some values of comprable types may also be uncomparable. For example, an interface value with a uncomparable dynamic type is an uncomparable value. Comparing an uncomparable value which type is comprable with itself will cause a panic at run time.

We can learn more about the detailed rules of comparisons from the article value conversions, assignments and comparisons in Go.

The key type of any map type must be a comparable type. So the above listed types can't be used as map key types.

Fact: Object-Oriented Programming In Go

Go is not a full-featured object-oriented programming language, but Go really supports some object-oriented programming styles. Please read the following listed articles for details:

Fact: Generic In Go

The generic functionalities in Go are limited to built-in types and functions. Up to now (v1.11), Go doesn't support generic for custom types and custom functions. Please read built-in generic in Go for details.


The Go 101 project is hosted on both github and gitlab. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.

Support Go 101 by playing Tapir's games. Cryptocurrency donations are also welcome:
Bitcoin: 1xucQbv5jujFPPwhyg395ri5yV71hx9g9
Ethereum: 0x5dc4aa2c2bbfaadae373dadcfca11b3358912212