Go Crash Course Part IX: Structs, Embedding and Methods
Mofi Rahman
Posted on May 18, 2021
Struct
If you are coming from a OOP language like Java finding struct in Go might make you relieved. Although it is possible to do most OOP like things in Go. It is not the Go way. Structs are not objects. Structs are values. Struct are a collection of fields.
type Circle struct {
X float64
Y float64
Radius float64
}
Here we have defined a struct type Circle
that has 3 fields. To use a struct we can declare a new Circle
variable like any other type.
c1 := Circle{
X: 15.0,
Y: 12.0,
Radius: 8.5,
}
c2 := Circle{15.0, 12.0, 8.5}
These two are identical definition. Notice in c1
we put the field name and in c2
we omit it. We can only omit field names if a) we initialize all the fields and b) we omit all the fields. For example this is illegal
c2 := Circle{15.0, Y: 18.0, 8.5} // This will not compile
Accessing Fields
If a struct has exported fields and methods, we can use it in any other package after importing the package. We can not use unexported method or fields outside the package. To use a field or a method on a struct type we can use the .
notation.
fmt.Println(c1.X, c1.Y, c1.Radius)
Methods
We talked about function in a previous post. Methods are just like functions but with a receiver argument.
func (c Circle) Area() float64 {
return c.Radius * c.Radius * math.Pi
}
func Area(c Circle) float64 {
return c.Radius * c.Radius * math.Pi
}
You can think of func (c Circle) Area() float64
as the same as func Area(c Circle) float64
. In the Area
method we will have a variable c
of type Circle
in scope, so we can use the value.
Receiver vs Pointer Receiver
We can declare methods with pointer receivers. Pointer receivers can be a bit confusing at times. You may think if you have a pointer receiver method defined you won't be able to use that method on the value of the struct. That is not the case. One of the main reasons you would want to use the pointer receiver is to mutate the value the other would be if the value is two large and value receiver would have to make a big copy operation. This StackOverflow Answer outlines it in much more details.
func (c *Circle) Area() float64 {
return c.Radius * c.Radius * math.Pi
}
func (c Circle) Area2() float64 {
return c.Radius * c.Radius * math.Pi
}
We can use the function as such
fmt.Println(c1.Area() == c1.Area2())
Embedded Structs
In Go we usually do no promote the idea of inheritance. In OOP inheritance is the concept of one type inheriting its methods and fields from another type. Instead what we do have is embedded structs. In Go we try to compose as much as possible. We embed a struct in another by not adding a name to it.
type Wheel struct {
Circle
Material string
Color string
X float64
}
With struct embedding we can use the methods and fields of Circle
type from Wheel
as if they were defined on the Wheel
type.
w1 := Wheel{
Circle: Circle{
Radius: 10.0,
X: 15.0,
},
Material: "Rubber",
Color: "Black",
X: 5.0,
}
fmt.Println(w1.Area())
Shadowing Embedded Structs
Let's take another look at our Wheel
type.
type Wheel struct {
Circle
Material string
Color string
X float64
}
We have a field X
of type float64
defined on Wheel
. We also have a embedded struct Circle
which also have field X
defined. What would happen if we tried to print w1.X
. Would it print 15
from the embedded Circle
type or 5
from X
defined on Wheel
type.
fmt.Println(w1.X) // 5
This will print 5. X
in Wheel
shadows the X
field in Circle
. We can still access the X
in Circle
by accessing fields using the .
notation.
fmt.Println(w1.Circle.X) // 15
We can also shadow methods from embedded structs. We can embed any number of structs. Although this will make it harder to read our code.
The main difference between this type of composition and inheritance is that inheritance tries create a is relationship where composition create a has relationship.
Next Steps
This is Part 9 of this Go crash course series.
Posted on May 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.