Structs in Go

go dev.to

Let's explore structs in Go. If you know Go's basic types like string, int, and bool, structs let you handle more complex data.
Say you're building a store app. You need a product's name, price, and in-stock status. Using separate variables quickly becomes messy.
Structs can simplify this. Picture structures are like a blueprint. It makes it possible to package all the related information in one neat and organized bundle.
Now let's dive into creating and using custom data types in Go.

Declaring Structs

Before using a struct, we first need to describe what it looks like. The result of defining a struct is not a piece of data, but the blueprint of data we want to store later on.
Let's take a look at the declaration of the Product struct:

package main

import "fmt"

// We use the 'type' keyword to define a new custom type.
// We name our type 'Product' and specify that it is a 'struct'.
type Product struct {
   Name    string  // Field for the product's name
   Price   float64 // Field for the price
   InStock bool    // Field to check if it's available
}
Enter fullscreen mode Exit fullscreen mode

Creating Struct Instances

Using the struct we just created, let's create a product. Using the struct blueprint to create a piece of data is referred to as creating an instance.

package main

import "fmt"

type Product struct {
   Name    string
   Price   float64
   InStock bool
}

func main() {
   // Creating an instance of Product using a "struct literal."
   item1 := Product{
       Name:    "Wireless Mouse",
       Price:   25.99,
       InStock: true,
   }

   // Creating an instance but leaving fields blank
   item2 := Product{
       Name: "Mousepad",
   }

   fmt.Println(item1)
   fmt.Println(item2)
}
Enter fullscreen mode Exit fullscreen mode

Expected Output:

{Wireless Mouse 25.99 true}
{Mousepad 0 false}
Enter fullscreen mode Exit fullscreen mode

You might be thinking Why does item2 show 0 and false when we didn’t set them?
This is one of Go’s most important behaviours: zero values. In Go, when you declare a variable but don’t give it a value, Go doesn’t leave it empty or undefined (which causes bugs in many other languages). Instead, Go automatically fills it with the zero value for that type.
Since we gave item2 a name, Go automatically sets the price to 0 and in stock to false . This is one of the features in Go that make it exceptional.
Accessing and Modifying Fields
When you want to modify or read information from a struct, you use a dot notation. Consider the dot to be opening up a particular drawer of your filing cabinet to view a folder.

package main

import "fmt"

type Product struct {
   Name    string
   Price   float64
   InStock bool
}

func main() {
   item := Product{
       Name:    "Keyboard",
       Price:   45.00,
       InStock: true,
   }

   // Reading a field
   fmt.Println("Original Price:", item.Price)

   // Modifying a field
   item.Price = 39.99

   fmt.Println("Discounted Price:", item.Price)
}
Enter fullscreen mode Exit fullscreen mode

Expected Output:

Original Price: 45
Discounted Price: 39.99
Enter fullscreen mode Exit fullscreen mode

Anonymous Structs

Sometimes we want to bundle up some information for one-time use. In such a case, we don’t want to have to go through the trouble of formally defining a whole blueprint. They are particularly useful when holding configuration settings, grouping test inputs, or temporarily organising data for a single function. We use anonymous structs in this way:

package main

import "fmt"

func main() {
   // Declaring and initializing a struct at the exact same time
   config := struct {
       Port string
       Env  string
   }{
       Port: "8080",
       Env:  "Production",
   }

   fmt.Println("Running on port:", config.Port)
}
Enter fullscreen mode Exit fullscreen mode

Expected Output:

Running on port: 8080
Enter fullscreen mode Exit fullscreen mode

This is a little confusing, if you are new to Go, because you create the blueprint, then use it to create an instance. Let's look at the code snippet again.

  config := struct { <- this is the blueprint opening brace
       Port string
       Env  string
   }{   <- this is the closing brace for the blueprint, followed by an opening brace.
       Port: "8080",
       Env:  "Production",
   }   <- this is the closing
Enter fullscreen mode Exit fullscreen mode

Think of this like defining a shape, then filling it. Keep in mind that this is a non-reusable type.

Nested (Embedded) Structs

Unlike other programming languages, Go has no “classes” or “inheritance”. Instead, Go employs a technique of packing smaller structs into larger structs, similar to Russian nesting dolls.

package main

import "fmt"

// The smaller struct
type Address struct {
   City    string
   Country string
}

// The larger struct
type User struct {
   Name    string
   Contact Address // Embedding the Address struct here
}

func main() {
   player := User{
       Name: "Alice",
       Contact: Address{
           City:    "Nairobi",
           Country: "Kenya",
       },
   }

   // Accessing the nested data
   fmt.Println(player.Name, "lives in", player.Contact.City)
}
Enter fullscreen mode Exit fullscreen mode

Expected Output:

Alice lives in Nairobi.
Enter fullscreen mode Exit fullscreen mode

Struct Comparison

A question might pop into your brain asking, “Are you able to compare two structs to determine whether they are equal or not?” Yes! Go supports the normal == operator, but only if all the fields within the struct are comparable.

package main

import "fmt"

type Product struct {
   Name  string
   Price float64
}

func main() {
   item1 := Product{Name: "Mug", Price: 12.50}
   item2 := Product{Name: "Mug", Price: 12.50}
   item3 := Product{Name: "Mug", Price: 15.00}

   fmt.Println("Are item1 and item2 identical?", item1 == item2)
   fmt.Println("Are item1 and item3 identical?", item1 == item3)
}
Enter fullscreen mode Exit fullscreen mode

Expected Output:

Are item1 and item2 identical? true
Are item1 and item3 identical? false
Enter fullscreen mode Exit fullscreen mode

Go looks at every single field. If everything matches perfectly, the structs are considered equal.

The Photocopy vs. The Original

This is the most essential concept for beginners in Go. Go’s default behavior is to send a struct by value into a function.
If you were to give a friend a copy of your notes, how would they look? If they add a mustache to the photocopied version, you are fine! This is the default behavior of Go. To change an original struct, the function has to be passed a pointer to it (that is, the original notebook).
Let’s observe both of them together:

package main

import "fmt"

type Product struct {
   Name  string
   Price float64
}

// Pass by Value (The Photocopy)
func failedDiscount(p Product) {
   p.Price = p.Price - 5.00
}

// Pass by Pointer (The Original)
func successfulDiscount(p *Product) {
   p.Price = p.Price - 5.00
}

func main() {
   item := Product{Name: "Book", Price: 20.00}

   failedDiscount(item) // Trying to discount the photocopy
   fmt.Println("After failed discount:", item.Price)

   successfulDiscount(&item) // Giving the function the original via '&'
   fmt.Println("After successful discount:", item.Price)
}
Enter fullscreen mode Exit fullscreen mode

Expected Output:

After failed discount: 20
After successful discount: 15
Enter fullscreen mode Exit fullscreen mode

Best Practices & Common Beginner Mistakes

  1. In Go, names of fields that begin with a capital letter are “exported” and available to other packages (as opposed to names that start with a lowercase letter, which are “private”). If it begins with a lower-case letter (e.g., name), it belongs only to the package it is in. If in any doubt, make them capitalized so that you can use them freely.
  2. Pointers for Modification: Keep in mind the photocopy rule. If your function will call to change or update a struct, then you must pass a pointer (*).

Summary & Your Mini-Challenge

You’ve made it! You’ve learned to structure unsorted variables into orderly, sensible structures. You know how to construct blueprints, make instances, navigate nested data, and update nested data correctly with pointers.

Source: dev.to

arrow_back Back to Tutorials