Intro
Structs in Golang is a composite data type that helps us define our own data types. You may recall having struct keyword in C & C+ where we can define the custom data type. C++ structs are bit different from C and in Go it’s completely different. Here’s how we can use struct to define our own data types:
1 | package person |
Thinking about Go structs as classes will be wrong because we cannot define methods inside structs (we can associate methods though called as explicit functions via value or pointer receivers) and Go does not support inheritance.
Go has visibility restrictions based on how we name the struct type and their fields. For example:
1 | package person |
In the above example, the person struct is private to the package and also the fields name, age are private to the package and cannot be accessed outside the package.
Creating Structs
We have already seen how we define structs in Go. Now let’s see how we can use them. There are several ways we can create variables of the structs definitions. Let’s explore them one by one.
Zero Value
When we simply create an uninitialized variable of the struct type, we call it a zero value struct. If we do something like: var p Person then we get a Person variable with zero value which means the fields are having the default values of the respective data type. The default values are "" for string and 0 for uint8.
Named Field Initialization
Here we declare the variable and initialize them with the field names. In the code example below, we can see that we have created a variable of type Person which is john and then we are using the field names to assign the respective values to it.
1 | john := Person { |
This is readable and cleanest approach that is also the preferred way to creating struct variables in Go!.
Positional Initialization
Alternatively, we can skip the field names while initializing the struct variables and use positional values. In the code example below, we can see that we are passing string value at position 0 and integer value at position 1. Go will automatically assign them to the respective fields.
1 | tom := Person{"Tom", 20} |
This is not an ideal way as it can create confusions about the field meanings. We should avoid this kind of variable creations of structs in Go.ß
Using new
We can also use the Go function new for creating a struct variable. It will return a pointer struct type.
1 | jerry := new(Person) |
This is very rare and if required a pointer type then we generally see use of
1 | jerry := &Person{} |
Reading & Updating
Reading struct values in Go is simple, but updating them correctly requires understanding how Go handles values and pointers. Structs are copied by default, so updates inside functions or methods require pointers to affect the original data.
Reading Structs
This is pretty simple, we can use the . (dot) syntax to access the values of the struct fields.
1 | func main() { |
Even if we have a pointer, reading values is easy.
1 | func main() { |
Updating Structs
There are a few different ways we can update the structs. They are:
- Direct update
- Updating via functions
- Struct Methods
Direct Update
We can directly assign new values to the structs.
1 | func main() { |
Updating inside Functions
A common mistake happens when updating struct values inside a function. Let me explain with code example:
1 | func updateAge(p Person) { |
To update the original struct, we must pass a pointer of the struct type.
1 | func updateAge(p *Person) { |
Struct Methods
We can associate methods (behaviors) to our structs. Struct methods lets us attach behavior to data, making code easier to understand and maintain. Go is not a traditional object oriented language, but it still allows us to write clean, organized, and modular code.
In Go, methods are functions with a receiver. When a method has a struct as its receiver, it is called a struct method.
The syntax is:
1 | func (receiver Type) methodName() { |
Let’s see how we can add methods for the structs.
1 | package main |
Value Receivers
When a method uses a value receiver, it works on a copy of the struct.
1 | tom := Person{Name: "Tom", Age: 25} |
The above code cannot modify any fields of the tom. On execution it will print Hello, my name is Tom.
Pointer Receivers
Alternatively, when we use pointer receivers allow methods to modify the original struct.
1 | p.Birthday() |
Encapsulation
Struct methods help achieve encapsulation by controlling how data is accessed or modified.
1 | type BankAccount struct { |
In the above code, if you look carefully then you will notice that balance is an unexported field for the BankAccount struct.
Best Practices
Here are a few things we should keep in mind while using struct methods:
- Keep methods small and focused
- Use pointer receivers consistently
- Avoid mixing value and pointer receivers unnecessarily
- Attach methods only when they logically belong to the struct
Nested Structs
A nested struct means defining a struct inside another struct, without creating a separate named type. This is also called an inline struct. Nested structs are very useful when our data has a natural structure, like a person having an address. For example:
1 | package main |
This pattern is useful when the nested data belongs only to one struct and has no meaning outside of it. (But mostly in read world Address has a distinct meaning and should be a separate struct).
Using Nested Struct
We can see via an example how we create and use the nested structs in Go.
1 | package main |
Strictly nested structs are best when:
- The inner data is only relevant to one struct
- We don’t need to reuse the inner structure elsewhere
- We want to keep your code local and simple
Anonymous Structs
An anonymous struct is a struct without a name, created and used directly where it is needed. Unlike regular structs, anonymous structs are not defined as reusable types. They are mainly used for short lived, local, or one-time data structures. Ideally we should keep them short and should not add nesting or embedding.
1 | person := struct { |
Anonymous structs are very common in Go and can be found at places like function returns, tests and JSON responses.
Outro
Now that we have covered a good volume of Go data types and inbuilt data structures, we can move to the concepts of Interfaces in Go! Stay Tuned.