Skip to content

Go (Golang) Tutorial 10 - Structs

Published:

In this tutorial, we’ll learn about structs in Go. Structs let you group related data together and create custom types, similar to classes in other languages.

Understanding Structs in Go

Basic Struct Syntax

To declare and initialize a struct in Go, use the type keyword to define a new type, followed by the type name (e.g., Book), and the struct keyword to specify its fields.

type Book struct {
    Title  string
    Author string
    Pages  int
}

myBook := Book{
    Title:  "The Go Programming Language",
    Author: "Alan A. A. Donovan",
    Pages:  380,
}

This example defines a Book struct with fields for title, author, and pages, and initializes an instance of it.

Alternatively, you can create an empty struct and assign values to its fields later.

var myBook Book

This will create an empty Book struct.

To add values to the struct fields, you can use the following syntax:

myBook.Title = "The Go Programming Language"
myBook.Author = "Alan A. A. Donovan"
myBook.Pages = 380

fmt.Println(myBook) // Output will be: {The Go Programming Language Alan A. A. Donovan 380}

Accessing and Modifying Struct Fields

You can read values from a struct using the dot notation.

title := myBook.Title
fmt.Println(title) // Output will be "The Go Programming Language"

You can also directly mutate the fields of a struct using the dot notation. For example:

myBook.Title = "New Title"
fmt.Println(myBook.Title) // Output will be "New Title"

Nested Structs

Structs can be nested within other structs to create more complex data structures.

type Library struct {
    Name  string
    Books []Book
}

myLibrary := Library{
    Name: "City Library",
    Books: []Book{
        {Title: "The Go Programming Language", Author: "Alan A. A. Donovan", Pages: 380},
        {Title: "Go in Action", Author: "William Kennedy", Pages: 300},
    },
}

This example shows a Library struct containing a slice of Book structs.

To print a struct with all its fields, you can use the %+v format specifier.

fmt.Printf("%+v", myLibrary)

// Output will be: {Name:City Library Books:[{Title:The Go Programming Language Author:Alan A. A. Donovan Pages:380} {Title:Go in Action Author:William Kennedy Pages:300}]}

Struct Methods

You can define methods on structs to add functionality. You write methods as functions with a special receiver argument, which is the struct type it belongs to.

func (b Book) Summary() string {
    return fmt.Sprintf("%s by %s, %d pages", b.Title, b.Author, b.Pages)
}

fmt.Println(myBook.Summary())

This method returns a formatted string summarizing the book’s details.

Modifying Struct Fields with Methods

To update struct fields in a method, use a pointer receiver. A pointer is a reference to the memory address of the value it points to, so if you don’t use a pointer receiver, the method will only modify a copy of the struct, not the original.

func (b *Book) UpdatePages(newPages int) {
    if newPages > 0 {
        b.Pages = newPages
    } else {
        fmt.Println("Invalid page count. Pages must be greater than zero.")
    }
}

myBook.UpdatePages(400)
fmt.Println(myBook.Pages) // Output will be 400

If we would not have used a pointer receiver, the changes would not have been reflected in the original struct.

func (b Book) UpdatePages(newPages int) {
   if newPages > 0 {
        b.Pages = newPages
    } else {
        fmt.Println("Invalid page count. Pages must be greater than zero.")
    }
}

myBook.UpdatePages(400)
fmt.Println(myBook.Pages) // Output will be 380

Comparing Structs

In Go, you can compare structs using the == operator. For structs to be equal, all their fields must match exactly.

book1 := Book{Title: "Go in Action", Author: "William Kennedy", Pages: 300}
book2 := Book{Title: "Go in Action", Author: "William Kennedy", Pages: 300}

if book1 == book2 {
    fmt.Println("The books are identical.")
} else {
    fmt.Println("The books are different.")
}

The output will be “The books are identical.” because the fields match exactly.

Composition and Interfaces

Go doesn’t support inheritance. Instead, it uses composition to build complex types from simpler ones and interfaces for flexible behavior.

Example of Composition

package main

import "fmt"

type Printer struct{}

func (p Printer) Print() {
    fmt.Println("Printing document...")
}

type Scanner struct{}

func (s Scanner) Scan() {
    fmt.Println("Scanning document...")
}

// A struct that combines both Printer and Scanner
type MultiFunctionDevice struct {
    Printer
    Scanner
}

func main() {
    // Create a new MultiFunctionDevice
    mfd := MultiFunctionDevice{}

    mfd.Scan()
    mfd.Print()
}

Anonymous Structs

Anonymous structs are useful for temporary data structures, such as when parsing JSON. They are ideal for one-time use.

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    jsonResponse := `{"Name": "Alice", "Age": 30}`

    // Use an anonymous struct directly
    person := struct {
        Name string
        Age  int
    }{}

    err := json.Unmarshal([]byte(jsonResponse), &person)
    if err != nil {
        fmt.Println("Error unmarshalling JSON:", err)
        return
    }

    fmt.Printf("Name: %s, Age: %d\n", person.Name, person.Age)
}

This example shows how to parse JSON data into an anonymous struct.

Conclusion

In this tutorial, we explored how to use structs in Go. We covered how to declare and initialize them, access and modify fields, nest structs, define methods, and compare structs. We also looked at using composition to build complex types. Finally, we briefly touched on anonymous structs for temporary data structures.

Full source code can be found here Github