Go Structs

Pointers

Go has pointers, with syntax similar to C:

  • The type *int is a pointer to an int value.
  • If p is a pointer, then *p refers to the value it points to.
  • Given a variable v, we can get a pointer to it with &v.

Pointers

Or in code,

x := 12.345
p := &x
fmt.Println(x)  // the variable's value
fmt.Println(&x) // pointer to the variable
fmt.Println(*p) // the pointed-to value
fmt.Println(p)  // the pointer
fmt.Printf("%T %T\n", x, p)

Output will look like:

12.345
0xc42001a0f0
12.345
0xc42001a0f0
float64 *float64

Pointers

Unlike in C, pointer arithmetic isn't allowed. This will not compile:

arr := [4]int{1,2,3,4}
fmt.Println(arr + 1)

A lot of the unsafe things you can do with C pointers disappear with that fact.

Pointers

If we give a function a pointer to a variable, it can change its value.

func OneAdder(n *int) {
	*n = *n + 1
}
a := 9
OneAdder(&a)
fmt.Println(a)
10

Pointers

This can be used to have arguments where variables are modified to return results:

var inputInt int
var inputFloat float64
fmt.Scanf("%d %g", &inputInt, &inputFloat)
fmt.Println(inputInt, inputFloat)

But if possible, multiple return values (return a,b,c) is going to be much nicer code.

Pointers

The new function can be used to create an instance of a type. It returns a pointer to the instance. Just creating a variable is similar, but gives you the instance itself.

var a uint
fmt.Printf("%T %v %v\n", a, a, &a)
b := new(uint)
fmt.Printf("%T %v %v\n", b, *b, b)
uint 0 0xc42001a128
*uint 0 0xc42001a130

Pointers

It may look like var (and :=) variables are on the stack and new variables are on the heap, but the Go FAQ makes it clear: it's none of your business. The compiler will put the value wherever it determines makes the most sense.

You just need to know that the data is stored in memory, and will be garbage collected when all references to it disappear.

Struct Pointers

Pointers to structs work mostly the same as other values. There are some important implications that we should examine.

Let's start with a struct and give it a custom string representation.

The .String() method is used for fmt.Println, the %v format, etc.

Struct Pointers

type MyData struct {
	length  float32
	count_p *int
	values  []int
}

func (data MyData) String() string {
	if data.count_p != nil {
		return fmt.Sprintf("MyData{%v, &%v, %v}",
			data.length, *data.count_p, data.values)
	} else {
		return fmt.Sprintf("MyData{%v, nil, %v}",
			data.length, data.values)
	}
}

The only interesting part: don't try to print the *data.count_p value if it's a nil pointer.

Struct Pointers

This works mostly as expected:

count := 7
md1 := MyData{length: 5.6, count_p: &count}
fmt.Printf("%T - %v\n", md1, md1)
md2 := new(MyData)
fmt.Printf("%T - %v\n", md2, md2)
main.MyData - MyData{5.6, &7, []}
*main.MyData - MyData{0, nil, []}

Struct Pointers

Something a little odd happened: calling a method (implicitly, .String()) on a struct and pointer-to-struct did the same thing:

fmt.Printf("%#v %#v\n", md2.String(), (*md2).String())
"MyData{0, nil, []}" "MyData{0, nil, []}"

For convenience, calling any value in a pointer to a struct is the same as using the actual value. Calling ptr.member is a shortcut for (*ptr).member.

Struct Pointers

The struct methods we have had so far cannot modify the struct's values. e.g.

func (data MyData) doubleLength1() {
	data.length *= 2
}
count := 6
data := MyData{2.3, &count, []int{7, 8, 9}}
fmt.Println(data)
data.doubleLength1()
fmt.Println(data)
MyData{2.3, &6, [7 8 9]}
MyData{2.3, &6, [7 8 9]}

Struct Pointers

The receiver argument on a method can also be a pointer. Then the method can follow the pointer to modify the struct.

func (data *MyData) doubleLength2() {
	data.length *= 2
}
fmt.Println(data)
data.doubleLength2()
fmt.Println(data)
MyData{2.3, &6, [7 8 9]}
MyData{4.6, &6, [7 8 9]}

Struct Pointers

A method with a pointer receiver can modify the struct, but otherwise is the same as any other method.

In the first example, the receiver was a struct; in the second a pointer, but because pointer.length is really (*pointer).length, the actual code looked the same.

Struct Pointers

In other words:

  • func (s SomeStruct) f() cannot modify s.
  • func (s *SomeStruct) f() can modify s.

We can see methods that might mutate the struct at a glance.

[But there are some more important details we'll see soon…]

Pass by Value

All function arguments in Go (including receiver arguments) are passed by value. That is, they are copied and the function gets a copy of the value.

This includes pointers: when a pointer is used as an argument, the pointer is copied but not the pointed-to value.

Pass by Value

Or with picture, suppose we start with this scenario:

pair := Pair{10, 11}
ptr := &pair
value and pointer

Pass by Value

Passing the struct as an argument:

usePair(pair)
pass by value

Pass by Value

Passing a pointer to the struct as an argument:

changePair(ptr) // or changePair(&pair)
pass pointer

Pass by Value

We can tell now why methods with non-pointer receiver arguments can't modify the struct: they actually can, but only their copy. That doesn't affect code outside the function call.

Pass by Value

But, let's return to this struct:

type MyData struct {
	length  float32
	count_p *int
	values  []int
}

The *int is a pointer: any function that has that pointer can change the value.

Pass by Value

For example:

func (data MyData) incrementCount() {
	*data.count_p += 1
}
fmt.Println(data)
data.incrementCount()
fmt.Println(data)
MyData{2.3, &6, [7 8 9]}
MyData{2.3, &7, [7 8 9]}

Note: not a pointer receiver argument, but the function did change the value.

Pass by Value

So, the only pointer receivers can modify rule isn't exactly true.

A non-pointer receiver method can't modify the struct, but it still has all of the values to work with, even if they're pointers.

Pass by Value

Another implication: passing a large struct by value might be expensive because a lot of data must be copied. Common advice is to pass pointers for large structs.

It's not clear to me that this is a useful optimization. At least in a simple example, the compiler figures it out just fine.

BenchmarkPassValue-12       253030304      4.72 ns/op
BenchmarkPassPointer-12     253861560      4.75 ns/op

References

There is (almost) no concept of a reference in Go the way C++ uses the term. In C++, two variables can refer to the same memory location:

int value = 123;
int & alias = value;
alias = 124;

cout << value << '\n';
cout << alias << '\n';
124
124

References

In Go, we can have many pointers to a particular memory location, but they work like pointers, not like plain variables.

References

Except array slices behave like references.

Let's start with an array and some slices of it:

array := [4]int{0, 10, 20, 30}
slice1 := array[:]
slice2 := array[2:4]
fmt.Println(array)
fmt.Println(slice1)
fmt.Println(slice2)
[0 10 20 30]
[0 10 20 30]
[20 30]

References

If we modify a slice, it changes the underlying array and any other slices of it.

slice2[0] = 999
fmt.Println(array)
fmt.Println(slice1)
fmt.Println(slice2)
[0 10 999 30]
[0 10 999 30]
[999 30]

References

Slices are references to (parts of) arrays.

That has implications to what can be mutable as well…

References

Let's return again to the struct that included an array slice and define some more methods on it:

type MyData struct {
	length  float32
	count_p *int
	values  []int
}
func (data MyData) appendValue(v int) {
	data.values = append(data.values, v)
}
func (data MyData) setFirstValue(v int) {
	data.values[0] = v
}

References

Neither has a pointer receiver. Will they do what they seem to do?

data = MyData{8.9, &count, []int{7, 8, 9}}
fmt.Println(data)
data.appendValue(10)
fmt.Println(data)
data.setFirstValue(10)
fmt.Println(data)
MyData{8.9, &7, [7 8 9]}
MyData{8.9, &7, [7 8 9]}
MyData{8.9, &7, [10 8 9]}

References

Why? This line changes the .values field (in the function's copy) to a new slice. That changes nothing outside the function.

data.values = append(data.values, v)

This updates the slice (and thus the underlying array). That is visible outside the function.

data.values[0] = v

References

The ways functions are/​aren't isolated from their callers has some complexity in Go.

I think all of the pieces of the complexity make sense, but require understanding how the language is designed.

P.S. The reference descriptions (done with arrays/​slices here) also apply to map values (which we haven't discussed) and values in channels.

Type Definitions

We have seen struct types declared:

type MyStruct struct { … }

We can also create a new declared type that refers to a built-in type:

type Length float64
type Area float64

Type Definitions

Variables of a declared type can be created with var or have types inferred if explicitly converted.

var distance Length = 16
surface := Area(54000)

These use memory exactly like the underlying type (in this case float64). The defined type can bring some more semantics and type-checking when compiling.

Type Definitions

Defined types can have methods declared, like structs. For example, custom string representations.

func (l Length) String() string {
	return fmt.Sprintf("%g metres", l)
}
func (a Area) String() string {
	return fmt.Sprintf("%g metres^2", a)
}
fmt.Println(distance)
fmt.Println(surface)
16 metres
54000 metres^2

Type Definitions

With a defined type, it can be more clear in your code what kind of value is being used. Compare the readability of the almost-equivalent:

d := Length(16.0)
a := Area(54000.0)
d := 16.0
a := 54000.0

Type Definitions

There is more type checking enabled by defined types: they are considered different types. This will not compile:

fmt.Println(distance < surface)

…with an error:

invalid operation: … (mismatched types Length and Area)

Type Definitions

A literal value of the underlying type will be automatically cast to a defined type. This will compile and work as you would expect:

fmt.Println(distance < 20.0)
true

But this will not compile:

maxDist := 20.0
fmt.Println(distance < maxDist)

Type Definitions

If you want to, you can insert a lot of meaning into the types in your code.

type Identifier uint
type Crc64 [8]byte
type Matrix [][]float64

Sometimes this will be worth it. Sometimes, it will just mean lots of extra type conversions.

Interfaces

Go doesn't have a subclass/​inheritance concept (exactly, but more later), but it does have interfaces. Let's work with an example…

Consider the previous Length and Area types (both with float64 underlying). Let's add one more type:

type StockItem struct {
	Mass float64
	Item string
}

func (s StockItem) String() string {
	return fmt.Sprintf("%g kg of %s", s.Mass, s.Item)
}

Interfaces

Let's add another method to each of those:

// Convert m to feet
func (l Length) ToImperial() float64 {
	return float64(l) * 0.3048
}

// Convert m^2 to acres
func (a Area) ToImperial() float64 {
	return float64(a / 4046.86)
}

// Convert item's mass to pounds
func (s StockItem) ToImperial() float64 {
	return float64(s.Mass) * 2.20462
}

Interfaces

These types now all have something in common: a .ToImperial() method that returns a float64, but are otherwise unrelated. We can represent this with an interface.

type Measurement interface {
	ToImperial() float64
}

i.e. anything that implements the Measurement interface must have that method.

Interfaces

An interface applies to any type with the right method(s). Types do not have to declare that they are implementing an interface: the compiler automatically checks and confirms when needed.

All of these already implement Measurement.

distance := Length(16.0)
area := Area(54000.0)
item := StockItem{4.5, "flour"}

Interfaces

With no changes to the type definitions, we can use them as Measurements.

var measure Measurement = distance
fmt.Printf("%T - %v\n", measure, measure.ToImperial())
main.Length - 4.8768

Interfaces

Interface types can be used for variables, or arrays, or anywhere else we need values of a certain type. e.g. an array of Measurement values:

measurements := [3]Measurement{distance, area, item}
for _, m := range measurements {
	fmt.Printf("%T - %v - %.3f\n", m, m, m.ToImperial())
}
main.Length - 16 metres - 4.877
main.Area - 54000 metres^2 - 13.344
main.StockItem - 4.5 kg of flour - 9.921

Interfaces

Notice that whenever we looked at the type of an interface value, we got the concrete type, not the interface type.

fmt.Printf("%T - %v\n", measure, measure.ToImperial())
main.Length - 4.8768

Interface values know their type: there is no question of which .ToImperial() is going to be called.

Interfaces

Another example: interfaces can be used as function arguments. So if we had an interface that meant can serialize/​deserialize as JSON like this,

type JSONable interface {
	ToJSON() string  // JSON representation of contents
	FromJSON(string) // replace contents with JSON data
}

… we could write utility functions to work with any implementing type:

func WriteJSON(value JSONable, filename string) { … }
func ReadJSON(filename string, value *JSONable) { … }

Implementing Interfaces

Let's try to implement that interface:

type Rectangle0 struct {
	width, height float32
}

func (r Rectangle0) ToJSON() string {
	return fmt.Sprintf("[%v, %v]", r.width, r.height)
}
func (r *Rectangle0) FromJSON(json string) {
	// TODO
}

But this does not implement the JSONable: a Rectangle0 type has a ToJSON method, but no FromJSON. The FromJSON only exists on a *Rectangle0 pointer.

Implementing Interfaces

The struct and pointer-to-struct working the same is a syntax convenience for us, but they are different types. The compiler finds the method we want when we need it, but that similarity doesn't carry over to implementing interfaces.

If we want to implement an interface, all of the methods must have the same receiver type.

Implementing Interfaces

With this definition, a *Rectangle does implement JSONable.

type Rectangle struct {
	width, height float32
}

func (r *Rectangle) ToJSON() string {
	return fmt.Sprintf("[%v, %v]", r.width, r.height)
}
func (r *Rectangle) FromJSON(json string) {
	// TODO
}
func (r Rectangle) Area() float32 {
	return r.height * r.width
}

Implementing Interfaces

Now we can use this as an interface instance. Suppose we have a function that returns an instance, but we know it's actually a Rectangle in our code:

func ReadJSONThing(jsonString string) JSONable { … }

Then we get the value we expect:

data := ReadJSONThing("[6.7, 8.9]")
fmt.Printf("%T - %v\n", data, data)
*main.Rectangle - &{6.7 8.9}

Implementing Interfaces

Then we can use it?

fmt.Println(data.Area())

No.

data.Area undefined (type JSONable has no field or method Area)

Implementing Interfaces

The interface doesn't have an .Area method. The compiler doesn't know what will be returned (except that it's a JSONable instance).

If we know the concrete type, we can make a type assertion. This works:

rect := data.(*Rectangle)
fmt.Println(rect.Area())

Implementing Interfaces

The syntax is . (like struct member access) and a type in parens. With the previous interface example, that might have been

meas := functionReturningMeasure()
length := meas.(Length)

Or equivalently,

length := functionReturningMeasure().(Length)

Implementing Interfaces

Is the Go compiler doing static or dynamic binding?

The only reason (I see) for a type assertion is to avoid runtime checks.

But if (for example) iterating over an array of interface values, there must be some kind of dynamic dispatch of the methods.

So… both? Performance suggests it's probably statically binding in newer versions of the compiler? Maybe only in simple cases?

Implementing Interfaces

My opinion on interfaces in Go…

  • At least we have some way to do polymorphic things: code can declare an interface with the stuff it needs, and then assume it's there.
  • Implicit implementation is cool: let the compiler figure it out.
  • The details of how interfaces are implemented are annoyingly specific.

Common Interfaces

There are a few interfaces that come up a lot and are worth knowing…

The error interface requires a Error() method that returns an error message (as a string).

The fmt.Stringer interface: has String() so it can be printed (or similar).

The io package contains interfaces that describe various aspects of like a file handle.

sort.Interface: has methods that allow sorting.

The Empty Interface

There is one common interface that isn't really special, but it gets used a lot: the empty interface: interface{}.

The empty interface imposes no restrictions on the type: every type in Go implements the empty interface.

The Empty Interface

The empty interface can be used for code that is doing things that apply to any type. For example, from A Tour of Go:

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}
describe(4)
describe("hello")
(4, int)
(hello, string)

The Empty Interface

That could include arrays that hold values of any type.

var values []interface{}
values = []interface{}{1, "two", 3.0, 4 + 4i}
for _, v := range values {
	describe(v)
}
(1, int)
(two, string)
(3, float64)
((4+4i), complex128)

Inheritance

Along with not exactly having classes, Go doesn't exactly have inheritance: one struct cannot extend another.

But, like with classes, there's an alternative.

Inheritance

Let's start with a struct that we want to act like a superclass:

type Person struct {
	fName, lName string
}

func (p Person) String() string {
	return fmt.Sprintf("%s, %s", p.lName, p.fName)
}

Inheritance

One struct can contain another as a member (obviously).

type Student struct {
	pers Person
	gpa  float32
}

But if we make the field anonymous, things get more interesting:

type Student struct {
	Person
	gpa float32
}

Inheritance

Now if we use the Student struct, there are some subclass-like features:

s := Student{Person{"Donnie", "Hester"}, 3.5}
fmt.Println(s)
fmt.Println(s.fName)
fmt.Println(s.gpa)
Hester, Donnie
Donnie
3.5

Note: we got the .String() and .fName from the Person, but the .gpa from the Student.

Inheritance

These structs are composed. When structs defined this way (with other structs as anonymous fields), Go will let us access their members (fields and methods) as if they were part of the composed struct.

The anonymous field actually gets named after its type. These are the same:

fmt.Println(s.Person.fName)
fmt.Println(s.fName)

Inheritance

The rule for inheritance is simple enough: also look in the anonymous fields when looking for a struct member.

There can be multiple anonymous fields, so multiple inheritance(-like behaviour) appears.

type Contract struct {
	annualPay uint
	startDate time.Time
}

type Employee struct {
	Person
	Contract
}

Inheritance

The composed struct is searched first, so we can override methods, but get to the superclass methods explicitly.

func (e Employee) String() string {
	return fmt.Sprintf("%s @ $%d", e.Person.String(),
		e.annualPay)
}
today := time.Now()
e := Employee{Person{"Arandeep", "Zamora"},
	Contract{100000, today}}
fmt.Println(e)
fmt.Println(e.fName, e.annualPay)
Zamora, Arandeep @ $100000
Arandeep 100000

Inheritance

Composed structs can even implement interfaces.

type NameHaver interface {
	Name() string
}

func (p Person) Name() string {
	return p.String()
}
p := Person{"Sonia", "Hanson"}
e := Employee{Person: Person{"Nellie", "Jordan"}}
var nh NameHaver = p
fmt.Println(nh)
nh = e
fmt.Println(nh)
Hanson, Sonia
Jordan, Nellie @ $0

Inheritance

Opinion: I'd call compose structs instead of inheritance another features where Go demonstrates elegant simplicity.

But a more serious OO programmer may disagree and find examples where it's not good enough.

Handling Errors

We have seen one way that errors can be dealt with in Go: use defer to specify code that must always run as a function exits.

Here, the file is closed if the function exits normally, or if the disk is full, etc.

func WriteJSON(value JSONable, filename string) {
	f, err := os.Create(filename)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	f.Write([]byte(value.ToJSON()))
}

Handling Errors

Go doesn't have exceptions, but it can panic, which is similar.

Idiomatic Go code doesn't panic as often as many languages throw exceptions: panic isn't meant to be used for things that are expected in the normal execution of the program.

Handling Errors

For most problems, Go functions return two values: the real return value, and an error value. We saw that here:

f, err := os.Create(filename)
if err != nil {
	panic(err)
}

The idea: if the error is nil, there was no problem and we can use the return value. If not, there was an error and we have to deal with it.

Using panic in this case was bad Go style.

Handling Errors

The .Write method also might have failed by returning an error, but we ignored it above.

We should have handled it in some useful way.

nBytes, err := f.Write([]byte(value.ToJSON()))
if err != nil {
	// error writing: do something about that.
}
// everything is okay.

Handling Errors

The if err != nil { … } pattern quickly becomes very common in Go code.