Structs and Interfaces

Although it would be possible for us to write programs only using Go's built-in data types, at some point it would become quite tedious. Consider a program that interacts with shapes:

```package main

import ("fmt"; "math")

func distance(x1, y1, x2, y2 float64) float64 {
a := x2 – x1
b := y2 – y1
return math.Sqrt(a*a + b*b)
}
func rectangleArea(x1, y1, x2, y2 float64) float64 {
l := distance(x1, y1, x1, y2)
w := distance(x1, y1, x2, y1)
return l * w
}
func circleArea(x, y, r float64) float64 {
return math.Pi * r*r
}
func main() {
var rx1, ry1 float64 = 0, 0
var rx2, ry2 float64 = 10, 10
var cx, cy, cr float64 = 0, 0, 5

fmt.Println(rectangleArea(rx1, ry1, rx2, ry2))
fmt.Println(circleArea(cx, cy, cr))
}```

Keeping track of all the coordinates makes it difficult to see what the program is doing and will likely lead to mistakes.

Structs

An easy way to make this program better is to use a struct. A struct is a type which contains named fields. For example we could represent a Circle like this:

```type Circle struct {
x float64
y float64
r float64
}```

The `type` keyword introduces a new type. It's followed by the name of the type (`Circle`), the keyword `struct` to indicate that we are defining a `struct` type and a list of fields inside of curly braces. Each field has a name and a type. Like with functions we can collapse fields that have the same type:

```type Circle struct {
x, y, r float64
}```

Initialization

We can create an instance of our new Circle type in a variety of ways:

`var c Circle`

Like with other data types, this will create a local Circle variable that is by default set to zero. For a `struct` zero means each of the fields is set to their corresponding zero value (`0` for `int`s, `0.0` for `float`s, `""` for `string`s, `nil` for pointers, …) We can also use the new function:

`c := new(Circle)`

This allocates memory for all the fields, sets each of them to their zero value and returns a pointer. (`*Circle`) More often we want to give each of the fields a value. We can do this in two ways. Like this:

`c := Circle{x: 0, y: 0, r: 5}`

Or we can leave off the field names if we know the order they were defined:

`c := Circle{0, 0, 5}`

Fields

We can access fields using the `.` operator:

```fmt.Println(c.x, c.y, c.r)
c.x = 10
c.y = 5```

Let's modify the `circleArea` function so that it uses a `Circle`:

```func circleArea(c Circle) float64 {
return math.Pi * c.r*c.r
}```

In main we have:

```c := Circle{0, 0, 5}
fmt.Println(circleArea(c))```

One thing to remember is that arguments are always copied in Go. If we attempted to modify one of the fields inside of the `circleArea` function, it would not modify the original variable. Because of this we would typically write the function like this:

```func circleArea(c *Circle) float64 {
return math.Pi * c.r*c.r
}```

And change main:

```c := Circle{0, 0, 5}
fmt.Println(circleArea(&c))```

Methods

Although this is better than the first version of this code, we can improve it significantly by using a special type of function known as a method:

```func (c *Circle) area() float64 {
return math.Pi * c.r*c.r
}```

In between the keyword `func` and the name of the function we've added a “receiver”. The receiver is like a parameter – it has a name and a type – but by creating the function in this way it allows us to call the function using the `.` operator:

`fmt.Println(c.area())`

This is much easier to read, we no longer need the `&` operator (Go automatically knows to pass a pointer to the circle for this method) and because this function can only be used with `Circle`s we can rename the function to just `area`.

Let's do the same thing for the rectangle:

```type Rectangle struct {
x1, y1, x2, y2 float64
}
func (r *Rectangle) area() float64 {
l := distance(r.x1, r.y1, r.x1, r.y2)
w := distance(r.x1, r.y1, r.x2, r.y1)
return l * w
}```

`main` has:

```r := Rectangle{0, 0, 10, 10}
fmt.Println(r.area())```

Embedded Types

A struct's fields usually represent the has-a relationship. For example a `Circle` has a `radius`. Suppose we had a person struct:

```type Person struct {
Name string
}
func (p *Person) Talk() {
fmt.Println("Hi, my name is", p.Name)
}```

And we wanted to create a new `Android` struct. We could do this:

```type Android struct {
Person Person
Model string
}```

This would work, but we would rather say an Android is a Person, rather than an Android has a Person. Go supports relationships like this by using an embedded type. Also known as anonymous fields, embedded types look like this:

```type Android struct {
Person
Model string
}```

We use the type (`Person`) and don't give it a name. When defined this way the `Person` struct can be accessed using the type name:

```a := new(Android)
a.Person.Talk()```

But we can also call any `Person` methods directly on the `Android`:

```a := new(Android)
a.Talk()```

The is-a relationship works this way intuitively: People can talk, an android is a person, therefore an android can talk.

Interfaces

You may have noticed that we were able to name the `Rectangle`'s `area` method the same thing as the `Circle`'s `area` method. This was no accident. In both real life and in programming, relationships like these are commonplace. Go has a way of making these accidental similarities explicit through a type known as an Interface. Here is an example of a `Shape` interface:

```type Shape interface {
area() float64
}```

Like a struct an interface is created using the `type` keyword, followed by a name and the keyword `interface`. But instead of defining fields, we define a “method set”. A method set is a list of methods that a type must have in order to “implement” the interface.

In our case both `Rectangle` and `Circle` have area methods which return `float64`s so both types implement the `Shape` interface. By itself this wouldn't be particularly useful, but we can use interface types as arguments to functions:

```func totalArea(shapes ...Shape) float64 {
var area float64
for _, s := range shapes {
area += s.area()
}
return area
}```

We would call this function like this:

`fmt.Println(totalArea(&c, &r))`

Interfaces can also be used as fields:

```type MultiShape struct {
shapes []Shape
}```

We can even turn `MultiShape` itself into a `Shape` by giving it an area method:

```func (m *MultiShape) area() float64 {
var area float64
for _, s := range m.shapes {
area += s.area()
}
return area
}```

Now a `MultiShape` can contain `Circle`s, `Rectangle`s or even other `MultiShape`s.

Problems

• What's the difference between a method and a function?

• Why would you use an embedded anonymous field instead of a normal named field?

• Add a new method to the `Shape` interface called `perimeter` which calculates the perimeter of a shape. Implement the method for `Circle` and `Rectangle`.

 ← Previous Index Next →