Structs
10 min read
- Authors
- Name
- Karan Pratap Singh
- @karan_6864
Table of Contents
In this tutorial, we will learn about structs.
So, a struct
is a user-defined type that contains a collection of named fields. Basically, it is used to group related data together to form a single unit.
If you're coming from an objected-oriented background, think of structs as lightweight classes which that support composition but not inheritance.
Defining
We can define a struct
like this:
type Person struct {}
We use the type
keyword to introduce a new type, followed by the name and then the struct
keyword to indicate that we're defining a struct.
Now, let's give it some fields:
type Person struct {
FirstName string
LastName string
Age int
}
And, if the fields have the same type, we can collapse them as well.
type Person struct {
FirstName, LastName string
Age int
}
Declaring and initializing
Now that we have our struct, we can declare it the same as other datatypes.
func main() {
var p1 Person
fmt.Println("Person 1:", p1)
}
$ go run main.go
Person 1: { 0}
As we can see, all the struct fields are initialized with their zero values. So the FirstName
and LastName
are set to ""
empty string and Age
is set to 0.
We can also initialize it as "struct literal".
func main() {
var p1 Person
fmt.Println("Person 1:", p1)
var p2 = Person{FirstName: "Karan", LastName: "Pratap Singh", Age: 22}
fmt.Println("Person 2:", p2)
}
For readability, we can separate by new line but this will also require a trailing comma.
var p2 = Person{
FirstName: "Karan",
LastName: "Pratap Singh",
Age: 22,
}
$ go run main.go
Person 1: { 0}
Person 2: {Karan Pratap Singh 22}
We can also initialize only a subset of fields.
func main() {
var p1 Person
fmt.Println("Person 1:", p1)
var p2 = Person{
FirstName: "Karan",
LastName: "Pratap Singh",
Age: 22,
}
fmt.Println("Person 2:", p2)
var p3 = Person{
FirstName: "Tony",
LastName: "Stark",
}
fmt.Println("Person 3:", p3)
}
$ go run main.go
Person 1: { 0}
Person 2: {Karan Pratap Singh 22}
Person 3: {Tony Stark 0}
As we can see, the age field of person 3 has defaulted to the zero value.
Without field name
Go structs also supports initialization without field names.
func main() {
var p1 Person
fmt.Println("Person 1:", p1)
var p2 = Person{
FirstName: "Karan",
LastName: "Pratap Singh",
Age: 22,
}
fmt.Println("Person 2:", p2)
var p3 = Person{
FirstName: "Tony",
LastName: "Stark",
}
fmt.Println("Person 3:", p3)
var p4 = Person{"Bruce", "Wayne"}
fmt.Println("Person 4:", p4)
}
But here's the catch, we will need to provide all the values during the initialization or it will fail.
$ go run main.go
# command-line-arguments
./main.go:30:27: too few values in Person{...}
var p4 = Person{"Bruce", "Wayne", 40}
fmt.Println("Person 4:", p4)
We can also declare an anonymous struct.
func main() {
var p1 Person
fmt.Println("Person 1:", p1)
var p2 = Person{
FirstName: "Karan",
LastName: "Pratap Singh",
Age: 22,
}
fmt.Println("Person 2:", p2)
var p3 = Person{
FirstName: "Tony",
LastName: "Stark",
}
fmt.Println("Person 3:", p3)
var p4 = Person{"Bruce", "Wayne", 40}
fmt.Println("Person 4:", p4)
var a = struct {
Name string
}{"Golang"}
fmt.Println("Anonymous:", a)
}
Accessing fields
Let's clean up our example a bit and see how we can access individual fields.
func main() {
var p = Person{
FirstName: "Karan",
LastName: "Pratap Singh",
Age: 22,
}
fmt.Println("FirstName", p.FirstName)
}
We can also create a pointer to structs as well.
func main() {
var p = Person{
FirstName: "Karan",
LastName: "Pratap Singh",
Age: 22,
}
ptr := &p
fmt.Println((*ptr).FirstName)
fmt.Println(ptr.FirstName)
}
Both statements are equal as in Go we don't need to explicitly dereference the pointer. We can also use the built-in new
function.
func main() {
p := new(Person)
p.FirstName = "Karan"
p.LastName = "Pratap Singh"
p.Age = 22
fmt.Println("Person", p)
}
$ go run main.go
Person &{Karan Pratap Singh 22}
As a side note, two structs are equal if all their corresponding fields are equal as well.
func main() {
var p1 = Person{"a", "b", 20}
var p2 = Person{"a", "b", 20}
fmt.Println(p1 == p2)
}
$ go run main.go
true
Exported fields
Now let's learn what is exported and unexported fields in a struct. Same as the rules for variables and functions, if a struct field is declared with a lower case identifier, it will not be exported and only be visible to the package it is defined in.
type Person struct {
FirstName, LastName string
Age int
zipCode string
}
So, the zipCode
field won't be exported. Also, the same goes for the Person
struct, if we rename it as person
, it won't be exported as well.
type person struct {
FirstName, LastName string
Age int
zipCode string
}
Embedding and composition
As we discussed earlier, Go doesn't necessarily support inheritance, but we can do something similar with embedding.
type Person struct {
FirstName, LastName string
Age int
}
type SuperHero struct {
Person
Alias string
}
So, our new struct will have all the properties of the original struct. And it should behave the same as our normal struct.
func main() {
s := SuperHero{}
s.FirstName = "Bruce"
s.LastName = "Wayne"
s.Age = 40
s.Alias = "batman"
fmt.Println(s)
}
$ go run main.go
{{Bruce Wayne 40} batman}
However, this is usually not recommended and in most cases, composition is preferred. So rather than embedding, we will just define it as a normal field.
type Person struct {
FirstName, LastName string
Age int
}
type SuperHero struct {
Person Person
Alias string
}
Hence, we can rewrite our example with composition as well.
func main() {
p := Person{"Bruce", "Wayne", 40}
s := SuperHero{p, "batman"}
fmt.Println(s)
}
$ go run main.go
{{Bruce Wayne 40} batman}
Again, there is no right or wrong here, but nonetheless, embedding comes in handy sometimes.
Struct tags
A struct tag is just a tag that allows us to attach metadata information to the field which can be used for custom behavior using the reflect
package.
Let's learn how we can define struct tags.
type Animal struct {
Name string `key:"value1"`
Age int `key:"value2"`
}
You will often find tags in encoding packages, such as XML, JSON, YAML, ORMs, and Configuration management.
Here's a tags example for the JSON encoder.
type Animal struct {
Name string `json:"name"`
Age int `json:"age"`
}
Properties
Finally, let's discuss the properties of structs.
Structs are value types. When we assign one struct
variable to another, a new copy of the struct
is created and assigned.
Similarly, when we pass a struct
to another function, the function gets its own copy of the struct
.
package main
import "fmt"
type Point struct {
X, Y float64
}
func main() {
p1 := Point{1, 2}
p2 := p1 // Copy of p1 is assigned to p2
p2.X = 2
fmt.Println(p1) // Output: {1 2}
fmt.Println(p2) // Output: {2 2}
}
Empty struct occupies zero bytes of storage.
package main
import (
"fmt"
"unsafe"
)
func main() {
var s struct{}
fmt.Println(unsafe.Sizeof(s)) // Output: 0
}