A Closer Look at Go (golang) Type System.

A detailed look of the go type system, with examples.

Let’s begin by asking a very fundamental question?

Why we need a Type?

Before answering that, we need to look at some of the primitive abstracted layers of Programming Language that we don’t deal with on daily basis nowadays.

How Close can we get to machine representation of data?

Binary zeroes and 1’s. That what a Machine understand. But Does it makes sense to us? It Doesn’t to me until you’re the guy who can see something like this (Matrix Fanboy Anyone?).

What happens when you attain Nirvana in Computer Science.

So we can abstract these binary zero’s & 1 and move one step up in the ladder.

Consider this assembly fragment.

Can you tell what are the types of data in registers R1, R2, R3 ?

You might hope that they’re integers because at the assembly language level it cannot be determined. There’s nothing that prevents R1, R2, and R3 from having arbitrary types, because they’re just a bunch of registers with zero and 1’s in them, and the add operation will be happy to take them and add them up even if it doesn’t make sense, and produce a bit pattern then stores into R1.

So this notion of type start at even more higher abstraction, in a higher level language like C, Go, Java, Python, JavaScript etc, and is the feature of language itself.

Some language performs this type checking at the runtime, while some perform it at the compile time.

So what is a type?

The notion of type does vary from programming language to programming language and can be expressed in a number of different ways, but roughly they all have some sort of consensus.

  1. A type is a set of values.
  2. A set of operations on those values, for example, type of integers we can add ( + ), subtract (  ) etc, while on the type of string we can concatenate, perform empty check etc.
So a language type system specifies which operations are valid for which types.

And the Goal of type checking is to ensure that operations are used only, with the correct types. By doing this type checking enforces the intended interpretation of values, because nothing else is going to check, as once we got the machine level code, it’s just a lot of 0’s and 1’s, and the machine will be happy to do whatever operations we tell it on those 0’s and 1’s.

Type System is there to enforce that the intended interpretations of those bit patterns and make sure that a bit pattern for integers don’t do any non integer operation on that and get something that is meaningless.

Type System in GO

There are some fundamental specs that governs the Type System In Go. We will be looking at some of the important one.

But instead of just putting down all of the Concepts at once, here I will try to have different examples covering some fundamental concept of Go Type System, and then will walk you through these examples while explaining some of the essential concepts.

Have a moment and look at these code snippets. Which one of these will compile and why or why not?

type system in go

I would like you to note down your answer and reasons, so at the end you and me together we can reason about this.


Named ( defined ) Type

types with name: such as int, int64, float32, string, bool, etc. These are predeclared.

Also any type that we create using the type declaration is also named typed.

var i int // named type
type myInt int // named type
var b bool // named type
A Named (defined) Type is always different from any other type.

Unnamed Type

Composite types — array, struct, pointer, function, interface, slice, map, and channel types are all of Unnamed Type.

[]string // unnamed type
map[string]string // unnamed type
[10]int // unnamed type

As they have no name, but have got a type literal description about how they are to be structured.

Underlying Type

Each type T has an underlying type.
If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its type declaration.

So line number

3 & 8 We have predeclared type of string so the underlying type will be T itself i.e string

5 & 7 We have a type literal so the underlying type will be T itself i.e map[string]int and *N pointer. *Note these type literal are also unnamed type

4, 6 & 10 T's underlying type is the underlying type of the type to which T refers in its type declaration. B refers to A hence string and so forth.

The case that needs to be looked again is, at line number 9.

type T map[S]int. As S underlying type is string. Shouldn’t the underlying type of type T map[S]int be map[string]int instead of map[S]int, because here we are talking about underlying unnamed type map[S]int and underlying type stop at first unnamed type ( or as the specs says “If T is a type literal, the corresponding underlying type is T itself” ).

You might be pondering why i’m putting so much stress on these specs of unnamed type, named (defined) type and underlying type because it plays an important role in the specs that we are going to discuss further to help us understand why the code snippets posted above will compile or will not even when the intents are mostly same.

Assignability

When a variable v can be assigned to a variable to type T.

assignability specs

while the conditions are self explanatory, let’s look at one of the rule in specs state that, when assigning,

both should have the same underlying type, and at least one of then is not a named type.

Lets look at the snippet problem of figure 4 and 5 again

So the above code will not compile and will give us compile time error.

8:4: cannot use ai (type aInt) as type int in assignment
9:13: cannot use i (type int) as type aInt in argument to printAiType

because the i is of named type int and ai is of named type aInt , even though their underlying type is same.

The snippet4 will compile, because m is of unnamed type and underlying type of both m and mMap are same.


Type Conversion

Type Conversion Specs.

Looking at the code from figure 3.

The above code will compile as both Meter and Centimeter are of integers type, and their underlying type are convertable between each other.

Before we look into the code from figure 1 and 2. Let’s take a look at one more fundamental specs governing type system in Go.

Type identity

Two types are either identical or different.

A defined type is always different from any other type. Otherwise, two types are identical if their underlying type literals are structurally equivalent.

So even predeclared named (defined) type int, int64, etc are not identical.

Then looking at the rule for conversion for struct,

ignoring struct tags , x's type and T have identical underlying types.

Notice the term “Identical Underlying Type”. Since the underlying type of field Meter.value is of int64 and field Centimeter.value is of int32 they are not identical as A defined type is always different from any other type.

So we will get the compilation error for snippet code from figure 2, while in snippet code of figure 1

The underlying type of field Meter.value is of int64 and field Centimeter.value is of int64. So they are identical. Hence conversion without any compilation error.


Hope this article proves helpful to you at providing some insight of Go Type System, as it’s has been for me while writing.

Learned something? Clap your 👏 to help others find this article.