DEV Community

Cover image for Go Course: Structs
Karan Pratap Singh
Karan Pratap Singh

Posted on • Originally published at karanpratapsingh.com

Go Course: Structs

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 {}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

And, if the fields have the same type, we can collapse them as well.

type Person struct {
    FirstName, LastName string
    Age                 int
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Person 1: {  0}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

For readability, we can separate by new line but this will also require a trialing comma.

    var p2 = Person{
        FirstName: "Karan",
        LastName:  "Pratap Singh",
        Age:       22,
    }
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Person 1: {  0}
Person 2: {Karan Pratap Singh 22}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Person 1: {  0}
Person 2: {Karan Pratap Singh 22}
Person 3: {Tony Stark 0}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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{...}
Enter fullscreen mode Exit fullscreen mode
    var p4 = Person{"Bruce", "Wayne", 40}

    fmt.Println("Person 4:", p4)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Person &{Karan Pratap Singh 22}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
true
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
{{Bruce Wayne 40} batman}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Hence, we can re-write our example with composition as well.

func main() {
    p := Person{"Bruce", "Wayne", 40}
    s := SuperHero{p, "batman"}

    fmt.Println(s)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
{{Bruce Wayne 40} batman}
Enter fullscreen mode Exit fullscreen mode

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"`
}
Enter fullscreen mode Exit fullscreen mode

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"`
}
Enter fullscreen mode Exit fullscreen mode

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}
}
Enter fullscreen mode Exit fullscreen mode

Empty struct occupies zero bytes of storage.

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var s struct{}
    fmt.Println(unsafe.Sizeof(s)) // Output: 0
}
Enter fullscreen mode Exit fullscreen mode

This article is part of my open source Go Course available on Github.

GitHub logo karanpratapsingh / learn-go

Master the fundamentals and advanced features of the Go programming language

Learn Go

Hey, welcome to the course, and thanks for learning Go. I hope this course provides a great learning experience.

This course is also available on my website and as an ebook on leanpub. Please leave a ⭐ as motivation if this was helpful!

Table of contents

What is Go?

Go (also known as Golang) is a programming language developed at Google in 2007 and open-sourced in 2009.

It focuses on simplicity, reliability, and efficiency. It was designed to combine the efficacy, speed…

Top comments (0)