Go has pointers, with syntax similar to C:
*int
is a pointer to an int
value.p
is a pointer, then *p
refers to the value it points to.v
, we can get a pointer to it with &v
.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
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.
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
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.
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
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.
Pointers to struct
s 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.
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.
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, []}
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
.
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]}
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]}
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.
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…]
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.
Or with picture, suppose we start with this scenario:
pair := Pair{10, 11} ptr := &pair
Passing the struct as an argument:
usePair(pair)
Passing a pointer to the struct as an argument:
changePair(ptr) // or changePair(&pair)
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.
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.
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.
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.
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
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
In Go, we can have many pointers to a particular memory location, but they work like pointers, not like plain variables.
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]
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]
Slices are references to (parts of) arrays.
That has implications to what can be mutable as well…
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 }
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]}
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
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.
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
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.
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
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
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)
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)
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.
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) }
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 }
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.
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"}
With no changes to the type definitions, we can use them as Measurement
s.
var measure Measurement = distance fmt.Printf("%T - %v\n", measure, measure.ToImperial())
main.Length - 4.8768
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
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.
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) { … }
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.
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.
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 }
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}
Then we can use it?
fmt.Println(data.Area())
No.
data.Area undefined (type JSONable has no field or method Area)
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())
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)
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?
My opinion on interfaces in Go…
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.
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 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)
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)
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.
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) }
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 }
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
.
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)
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 }
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
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
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.
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())) }
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.
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.
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.
The if err != nil { … }
pattern quickly becomes very common in Go code.