Learn Go: Defining Structs in Golang #23
Structs can be defined and used in a few different ways. In this tutorial, well take a look at these techniques
Building abstractions around concrete details is the greatest tool that a programming language can give to a developer.
Structs allow Go developers to describe the world in which a Go program operates. Instead of reasoning about strings describing a Street, City, or a PostalCode, structs allow us to instead talk about an Address.
They serve as a natural nexus for documentation in our efforts to tell future developers (ourselves included) what data is important to our Go programs and how future code should use that data appropriately. Structs can be defined and used in a few different ways. In this tutorial, we’ll take a look at each of these techniques.
To create a new struct, you must first give Go a blueprint that describes the fields the struct contains.
This struct definition usually begins with the keyword type followed by the name of the struct. After this, use the struct keyword followed by a pair of braces {} where you declare the fields the struct will contain.
Once you have defined the struct, you are then able to declare variables that use this struct definition. This example defines a struct and uses it:
package main
import "fmt"
type Creature struct {
Name string
}
func main() {
c := Creature{
Name: "Sammy the Shark",
}
fmt.Println(c.Name)
}
When you run this code, you will see this output:
Fields of a struct follow the same exporting rules as other identifiers within the Go programming language.
If a field name begins with a capital letter, it will be readable and writeable by code outside of the package where the struct was defined. If the field begins with a lowercase letter, only code within that struct’s package will be able to read and write that field.
This example defines fields that are exported and those that are not:
package main
import "fmt"
type Creature struct {
Name string
Type string
password string
}
func main() {
c := Creature{
Name: "Sammy",
Type: "Shark",
password: "secret",
}
fmt.Println(c.Name, "the", c.Type)
fmt.Println("Password is", c.password)
}
output
Sammy the Shark
Password is secret
Within the same package, we are able to access these fields, as this example has done. Since main is also in the main package, it’s able to reference c.password and retrieve the value stored there. It’s common to have unexported fields in structs with access to them mediated by exported methods.
Creating anonymous structures
An anonymous only creates a new struct variable emp and does not define any new struct type.
package main
import (
"fmt"
)
func main() {
emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
fmt.Println("Employee 3", emp3)
}
In line no 8. of the above program, an anonymous structure variable emp is defined. As we have already mentioned, this structure is called anonymous because it only creates a new struct variable emp and does not define any new struct type.
Employee: {Andreah Nikola 31 5000}
In addition to defining a new type to represent a struct, you can also define an inline struct. These on-the-fly struct definitions are useful in situations where inventing new names for struct types would be wasted effort.
Inline struct definitions appear on the right-hand side of a variable assignment. You must provide an instantiation of them immediately after by providing an additional pair of braces with values for each of the fields you define.
The example that follows shows an inline struct definition:
package main
import "fmt"
func main() {
c := struct {
Name string
Type string
}{
Name: "Sammy",
Type: "Shark",
}
fmt.Println(c.Name, "the", c.Type)
}
The output from this example will be:
Rather than defining a new type describing our struct with the type keyword, this example defines an inline struct by placing the struct definition immediately following the short-assignment operator, :=.
We define the fields of the struct as in previous examples, but then we must immediately supply another pair of braces and the values that each field will assume. Using this struct is now exactly the same as before—we can refer to field names using dot notation.
It is possible that a struct contains a field which in turn is a struct. These kind of structs are called as nested structs.
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}
func main() {
var p Person
p.name = "Mike"
p.age = 15
p.address = Address {
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:",p.age)
fmt.Println("City:",p.address.city)
fmt.Println("State:",p.address.state)
}
The Person struct in the above program has a field address which in turn is a struct. This program outputs
Name: Mike
Age: 15
City: Chicago
State: Illinois
Fields that belong to a anonymous struct field in a structure are called promoted fields since they can be accessed as if they belong to the structure which holds the anonymous struct field. I can understand that this definition is quite complex so lets dive right into some code to understand this :).
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
In the above code snippet, the Person struct has an anonymous field Address which is a struct. Now the fields of the Address struct namely city and state are called promoted fields since they can be accessed as if they are directly declared in the Person struct itself.
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
func main() {
var p Person
p.name = "Mike"
p.age = 15
p.Address = Address{
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}
In line no. 26 and 27 of the above program, the promoted fields city and state are accessed as if they are declared in the structure p itself using the syntax p.city and p.state. This program outputs,
Name: Mike
Age: 15
City: Chicago
State: Illinois
You can get a pointer to a struct using the & operator -
package main
import (
"fmt"
)
type Student struct {
RollNumber int
Name string
}
func main() {
// instance of student struct type
s := Student{11, "Jack"}
// Pointer to the student struct
ps := &s
fmt.Println(ps)
// Accessing struct fields via pointer
fmt.Println((*ps).Name)
fmt.Println(ps.Name) // Same as above: No need to explicitly dereference the pointer
ps.RollNumber = 31
fmt.Println(ps)
}
# Output
&{11 Jack}
Jack
Jack
&{31 Jack}
As demonstrated in the above example, Go lets you directly access the fields of a struct through the pointer without explicit dereference.
Structs are value types. When you assign one struct variable to another, a new copy of the struct is created and assigned. Similarly, when you pass a struct to another function, the function gets its own copy of the struct.
package main
import "fmt"
type Point struct {
X float64
Y float64
}
func main() {
// Structs are value types.
p1 := Point{10, 20}
p2 := p1 // A copy of the struct `p1` is assigned to `p2`
fmt.Println("p1 = ", p1)
fmt.Println("p2 = ", p2)
p2.X = 15
fmt.Println("\nAfter modifying p2:")
fmt.Println("p1 = ", p1)
fmt.Println("p2 = ", p2)
}
# Output
p1 = {10 20}
p2 = {10 20}
After modifying p2:
p1 = {10 20}
p2 = {15 20}
Structs are collections of heterogenous data defined by programmers to organize information. Most programs deal with enormous volumes of data, and without structs, it would become difficult to remember which string or int variables belonged together or which were different.
The next time that you find yourself juggling groups of variables, ask yourself if perhaps those variables would be better grouped together using a struct. Those variables may have been describing some higher-level concepts all along.