This is the fifth entry of my weekly series Learning Go. Last week I covered a few more pieces of the Slice
and Map
type. This week I will be talking about the Struct
and Interface
types.
Struct
A struct
is a data structure that allows you to compose values of different types. Because of that, a struct
is a great way to aggregate data. From a computer science perspective, a struct
in Go is considered a composite data type.
This simply means that this is a data type which can be constructed using the language’s primitive data types (string
, int
, etc), or other composite types. Let’s see one in action.
In this example I will be creating a struct
with primitive data types:
package main
import (
"fmt"
)
type car struct {
model string
color string
year int
}
func main() {
c := car{
model: "tacoma",
color: "white",
year: 2020,
}
fmt.Println(c)
// {tacoma white 2020}
}
In the example above, I am creating a new struct
of type car
.
- first, I declare that I am creating a new
type
- then I declare an identifier for this
type
, in this case, ourtype
iscar
- we declare our new type,
car
, to have the underlying type ofstruct
- next, we list out the
field names
paired with theirtype
Anonymous struct
Like many things in programming, there is more than one way to do something. The same can be said about creating a struct
. If you are wanting to use a struct
for a specific scope, there is a short-hand way to declare them.
package main
import "fmt"
func main() {
c := struct {
model string
color string
year int
}{
model: "tacoma",
color: "white",
year: 2020,
}
fmt.Println(c)
// {tacoma white 2020}
}
Let me walk you through what is happening in this example:
- we are declaring a new variable,
c
of typestruct
- then, inside the brackets
{}
, on the left-hand side, we declare our field names - on the right-hand side, we declare the type of each respective field name
- last, and most importantly, inside another set of brackets
{}
we declare the name and the value of these field names
Important note: you must place a comma after each entry in a struct
, or you will get an error from the compiler that looks a little bit like this:
syntax error: unexpected newline, expecting comma or }
Method sets
Methods are used heavily in programming and that is no different in Go. Thinking in terms of traditional Object Oriented paradigms, a method is defined and called in relation to the Class it was defined in.
In Go, a type may have a method associated with it, most commonly with a struct
. Let’s take a look at an example using a method
of a struct
type:
package main
import (
"fmt"
)
type toyota struct {
model string
color string
year int
}
func (t toyota) start() {
fmt.Println("vroom vroom")
}
func main() {
t := toyota{
model: "tacoma",
color: "white",
year: 2020,
}
t.start()
// vroom vroom
}
- we define a new
type
with the identifiertoyota
with an underlying type ofstruct
- using the
func
keyword, we create a new function - next we see
(t toyota)
, pay attention totoyota
here, this is what is called a receiver type - this means this method can only be called by atoyota
type - in this example the
t
is a value receiver - it is possible to use a pointer receiver as well - using dot notation we can pull values from
t
- I will show you how in the next example below
package main
import (
"fmt"
)
type toyota struct {
model string
color string
year int
}
func (t toyota) start() {
fmt.Println("Hey! I'm a ", t.color, t.year, t.model)
}
func main() {
t := toyota{
model: "tacoma",
color: "white",
year: 2020,
}
t.start()
// Hey I'm a white 2020 tacoma
}
This example is identical to the previous; however, the change to note here is what is happening inside of the start
method.
- we see that we still have a receiver type of
toyota
with a receiver valuet
- if we take a look at the
toyota
type, we see that it has three field names:model
,color
, andyear
- inside of
func
main
we are creating a new variable namedt
- using a composite literal, we assign the variable
t
to be of typetoyota
and assign the valuestacoma
,white
, and2020
to their respective field names - this is where the magic happens: using dot notation, we call the
start
method fromt
- because
t
is of typetoyota
it has access to thestart
method - inside of
start
we are again using dot notation to print out the values of the fields found intoyota
Interfaces
An interface
is both a type
and how you name a group of methods in Go. Let’s jump right into an example to explain:
package main
import (
"fmt"
)
type car interface {
start() string
}
type toyota struct {
model string
}
type subaru struct {
model string
}
func (t toyota) start() string {
return t.model
}
func (s subaru) start() string {
return s.model
}
func getModel(c car) {
fmt.Println(c.start())
}
func main() {
t := toyota{model: "tacoma"}
s := subaru{model: "forester"}
getModel(t)
// tacoma
getModel(s)
// forester
}
- I start by creating a new
interface
- I do this by writing thetype
keyword, followed by the identifiercar
, and lastly the underlying type struct - next, I declare two
struct
types
,toyota
andsubaru
- they both have a field namedmodel
with the typestring
- I create two methods that are both called
start
and have value receivers and accept their respective receiver typetoyota
andsubaru
- I create a function named
getModel
that takes a value of typecar
as a parameter - inside of the
getModel
function, I print out the returned value of thestart
method - in the
main
function I declare two variables,t
ands
- using a composite literal,
t
is assigned to the value of typetacoma
with the field namemodel
and respective valuetacoma
- the same process is done on the next line, the only differences being the variable is named
s
and the type issubaru
- you might have noticed that both the
tacoma
andsubaru
types have a method namedstart
- since
start
is a part of thecar
interface
, both thetacoma
andsubaru
types can also be of typecar
- last, we invoke the
getModel
function twice, first by passing int
as an argument, and then by passings
as an argument - the value of the field name
model
is returned fort
ands
In Summary
There are so many ways to optimize and organize your code in Go.
The struct
data type helps us compartmentalize our code by common values and allows us to aggregate values of multiple types, all under one type. How cool is that?
While struct
allows us to group data creatively, interface
allows us to group functionality between our struct
values. Thus allowing our code to have a deeper reach throughout our codebase. Now, creating methods that can run functionality across multiple struct
types is a painless exercise.
Next week I will be sharing my experience with functions in Go, see you then!