One of the most fundamental things you can learn in a programming language is how to create data structures to store similar data around a similar concept.
In Go, those are called structs. Custom structs let you better control the data being passed around your code. In this article, I'll show you why you'd want to use structs, how to create them, and how to extend them with methods.
Let’s assume you have a function that takes 10 parameters that describe a car with various attributes:
package main
import (
"fmt"
)
func DescribeCar(make, model string, year int, color string, mileage float64, isElectric bool, engineType string, horsepower int, features []string, price float64) {
// Print out the car details
fmt.Println("Car Make:", car.Make)
fmt.Println("Car Model:", car.Model)
fmt.Println("Car Year:", car.Year)
fmt.Println("Car Color:", car.Color)
fmt.Println("Car Mileage:", car.Mileage)
fmt.Println("Is the Car Electric:", car.IsElectric)
fmt.Println("Car Engine Type:", car.EngineType)
fmt.Println("Car Horsepower:", car.Horsepower)
fmt.Println("Car Features:", car.Features)
fmt.Println("Car Price:", car.Price)
}
func main() {
DescribeCar("Toyota", "Camry", 2022, "Red", 15000.5, false, "V6", 268, []string{"Leather Seats", "Sunroof", "Navigation"}, 25000.00)
}
After two or three parameters in a function, the function definition starts to look pretty nasty. This can easily be cleaned up with a struct that contains all of the attributes of a car.
How to create a custom struct in Go
Creating a struct is quite simple. Using the example from above, we can create a Car struct like so:
type Car struct {
Make string
Model string
Year int
Color string
Mileage float64
IsElectric bool
EngineType string
Horsepower int
Features []string
Price float64
}
Now instead of passing in 10 parameters to a single func, we can create a new Car
variable, set the values of the fields on that variable, and pass that in instead:
func DescribeCar(car Car) {
// Print out the car details
fmt.Println("Car Make:", car.Make)
fmt.Println("Car Model:", car.Model)
fmt.Println("Car Year:", car.Year)
fmt.Println("Car Color:", car.Color)
fmt.Println("Car Mileage:", car.Mileage)
fmt.Println("Is the Car Electric:", car.IsElectric)
fmt.Println("Car Engine Type:", car.EngineType)
fmt.Println("Car Horsepower:", car.Horsepower)
fmt.Println("Car Features:", car.Features)
fmt.Println("Car Price:", car.Price)
}
func main() {
car := Car{
Make: "Toyota",
Model: "Camry",
Year: 2022,
Color: "Red",
Mileage: 15000.5,
IsElectric: false,
EngineType: "V6",
Horsepower: 268,
Features: []string{"Leather Seats", "Sunroof", "Navigation"},
Price: 25000.00,
}
DescribeCar(car)
}
Extending a struct with a custom method
If the DescribeCar
method is something we want to be able to do on all cars, you can also extend the struct with a method. By adding (car *Car)
we’re telling Go that we want to be able to call this func on an existing object like so:
func (car *Car) DescribeCar() {
// Print out the car details
fmt.Println("Car Make:", car.Make)
fmt.Println("Car Model:", car.Model)
fmt.Println("Car Year:", car.Year)
fmt.Println("Car Color:", car.Color)
fmt.Println("Car Mileage:", car.Mileage)
fmt.Println("Is the Car Electric:", car.IsElectric)
fmt.Println("Car Engine Type:", car.EngineType)
fmt.Println("Car Horsepower:", car.Horsepower)
fmt.Println("Car Features:", car.Features)
fmt.Println("Car Price:", car.Price)
}
func main() {
car := Car{
Make: "Toyota",
Model: "Camry",
Year: 2022,
Color: "Red",
Mileage: 15000.5,
IsElectric: false,
EngineType: "V6",
Horsepower: 268,
Features: []string{"Leather Seats", "Sunroof", "Navigation"},
Price: 25000.00,
}
car.DescribeCar()
}
Using pointer for optional values
If you try and use a struct without specifying a value for one of the fields, Go will still create it, however it will use the default value. For instance, if I create a car without specifying a year, it will default the year to 0. This can especially be problematic when converting the object to JSON and expect values to be null.
To get around this, you can actually use a pointer as the field type like so:
type Car struct {
Make string
Model string
Year int
Color string
Mileage *float64 // Since this is a pointer, its default value is 'nil'
IsElectric bool
EngineType string
Horsepower int
Features []string
Price float64
}
Now we can update the DescribeCar()
method to optionally log out the value of Mileage
. Notice how I’m also dereferencing the value to get the actual number instead of the memory address of the pointer:
func (car *Car) DescribeCar() {
// Print out the car details
fmt.Println("Car Make:", car.Make)
fmt.Println("Car Model:", car.Model)
fmt.Println("Car Year:", car.Year)
fmt.Println("Car Color:", car.Color)
if car.Mileage != nil {
fmt.Println("Car Mileage:", *car.Mileage)
}
fmt.Println("Is the Car Electric:", car.IsElectric)
fmt.Println("Car Engine Type:", car.EngineType)
fmt.Println("Car Horsepower:", car.Horsepower)
fmt.Println("Car Features:", car.Features)
fmt.Println("Car Price:", car.Price)
}