Pointers and structures
Pointers are the number one source of a headache of every C or C++ programmer. But they are one of the main tools to achieve high-performance code in non-garbage-collected languages. Fortunately for us, Go's pointers have achieved the best of both worlds by providing high-performance pointers with garbage-collector capabilities and easiness.
On the other side for its detractors, Go lacks inheritance in favor of composition. Instead of talking about the objects that are in Go, your objects have other . So, instead of having a car
structure that inherits the class vehicle
(a car is a vehicle), you could have a vehicle
structure that contains a car
structure within.
What is a pointer? Why are they good?
Pointers are hated, loved, and very useful at the same time. To understand what a pointer is can be difficult so let's try with a real world explanation. As we mentioned earlier in this chapter, a pointer is a like a mailbox. Imagine a bunch of mailboxes in a building; all of them have the same size and shape but each refers to a different house within the building. Just because all mailboxes are the same size does not mean that each house will have the same size. We could even have a couple of houses joined, a house that was there but now has a license of commerce, or a house that is completely empty. So the pointers are the mailboxes, all of them of the same size and that refer to a house. The building is our memory and the houses are the types our pointers refer to and the memory they allocate. If you want to receive something in your house, it's far easier to simply send the address of your house (to send the pointer) instead of sending the entire house so that your package is deposited inside. But they have some drawbacks as if you send your address and your house (variable it refers to) disappears after sending, or its type owner change--you'll be in trouble.
How is this useful? Imagine that somehow you have 4 GB of data in a variable and you need to pass it to a different function. Without a pointer, the entire variable is cloned to the scope of the function that is going to use it. So, you'll have 8 GB of memory occupied by using this variable twice that, hopefully, the second function isn't going to use in a different function again to raise this number even more.
You could use a pointer to pass a very small reference to this chunk to the first function so that just the small reference is cloned and you can keep your memory usage low.
While this isn't the most academic nor exact explanation, it gives a good view of what a pointer is without explaining what a stack or a heap is or how they work in x86 architectures.
Pointers in Go are very limited compared with C or C++ pointers. You can't use pointer arithmetic nor can you create a pointer to reference an exact position in the stack.
Pointers in Go can be declared like this:
number := 5
Here number := 5
code represents our 4 GB variable and pointer_to_number
contains the reference (represented by an ampersand) to this variable. It's the direction to the variable (the one that you put in the mailbox of this house/type/variable
). Let's print the variable pointer_to_number
, which is a simple variable:
println(pointer_to_number) 0x005651FA
What's that number? Well, the direction to our variable in memory. And how can I print the actual value of the house? Well, with an asterisk (*)
we tell the compiler to take the value that the pointer is referencing, which is our 4 GB variable.
println(*pointer_to_number) 5
Structs
A struct is an object in Go. It has some similarities with classes in OOP as they have fields. Structs can implement interfaces and declare methods. But, for example, in Go, there's not inheritance. Lack of inheritance looks limiting but in fact, composition over inheritance was a requirement of the language.
To declare a structure, you have to prefix its name with the keyword type
and suffix with the keyword struct
and then you declare any field or method between brackets, for example:
type Person struct { Name string Surname string Hobbies []string id string }
In this piece of code, we have declared a Person
structure with three public fields (Name
, Age
, and Hobbies
) and one private field (id
, if you recall the Visibility section in this chapter, lowercase fields in Go refers to private fields are just visible within the same package). With this struct
, we can now create as many instances of Person
as we want. Now we will write a function called GetFullName
that will give the composition of the name and the surname of the struct
it belongs to:
func (person *Person) GetFullName() string { return fmt.Sprintf("%s %s", person.Name, person.Surname) } func main() { p := Person{ Name: "Mario", Surname: "Castro", Hobbies: []string{"cycling", "electronics", "planes"}, id: "sa3-223-asd", } fmt.Printf("%s likes %s, %s and %s\n", p.GetFullName(), p.Hobbies[0], p.Hobbies[1], p.Hobbies[2]) }
Methods are defined similarly to functions but in a slightly different way. There is a(p *Person)
that refers to a pointer to the created instance of the struct
(recall the Pointers section in this chapter). It's like using the keyword this
in Java or self
in Python when referring to the pointing object.
Maybe you are thinking why does (p *Person
) have the pointer operator to reflect that p
is actually a pointer and not a value? This is because you can also pass Person by value by removing the pointer signature, in which case a copy of the value of Person is passed to the function. This has some implications, for example, any change that you make in p if you pass it by value won't be reflected in source p
. But what about our GetFullName()
method?
func (person Person) GetFullName() string { return fmt.Sprintf("%s %s", person.Name, person.Surname) }
Its console output has no effect in appearance but a full copy was passed before evaluating the function. But if we modify person
here, the source p
won't be affected and the new person
value will be available only on the scope of this function.
On the main
function, we create an instance of our structure called p
. As you can see, we have used implicit notation to create the variable (the :=
symbol). To set the fields, you have to refer to the name of the field, colon, the value, and the comma (don't forget the comma at the end!). To access the fields of the instantiated structure, we just refer to them by their name like p.Name
or p.Surname
. You use the same syntax to access the methods of the structure like p.GetFullName()
.
The output of this program is:
$ go run main.go Mario Castro likes cycling, electronics and planes
Structures can also contain another structure (composition) and implement interface methods apart from their own but, what's an interface method?