A Tutorial for Learning Structs in Go

Traducciones al Español
Estamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
Create a Linode account to try this guide with a $ credit.
This credit will be applied to any valid services used during your first  days.

Introduction

Go’s array, slice, and map types can be used to group multiple elements, but they cannot hold values of multiple data types. When you need to group different types of variables and create new data types, you can use structs.

Note
Go does not have a concept of classes from other object oriented languages. Structs will be used in similar ways as classes, with important differences. For example, there is no class inheritance feature in Go.

In this guide you will:

Before You Begin

To run the examples in this guide, your workstation or server will need to have Go installed, and the go CLI will need to be set in your terminal’s PATH:

If you prefer to experiment with Go without installing it first, you can run the examples found in this guide using the Go Playground.

An introductory-level knowledge of Go is assumed by this guide. If you’re just getting started with Go, check out our Learning Go Functions, Loops, and Errors tutorial.

Note
This guide was written with Go version 1.13.

A Simple Struct

The various elements of a struct are called the fields of the struct. The following Go program defines and uses a new struct type called Employee, which is composed of an employee’s first name and their employee ID. The program then instantiates this type:

File: employee.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func main() {
    var nathan Employee
    fmt.Println(nathan)
    nathan = Employee{FirstName: "Nathan", employeeID: 8124011}
    fmt.Println(nathan)

    var heather Employee = Employee{FirstName: "Heather"}
    fmt.Println(heather)

    mihalis := Employee{"Mihalis", 1910234}
    fmt.Println(mihalis)
}
Note
Structs, in particular, and Go types, in general, are usually defined outside the main() function in order to have a global scope and be available to the entire Go package, unless you want to clarify that a type is only useful within the current scope and is not expected to be used elsewhere in your code.

The output of employee.go will be:

go run employee.go
{ 0}
{Nathan 8124011}
{Heather 0}
{Mihalis 1910234}

The example illustrates some (but not all) of the ways a struct can be created:

  • When the variable nathan is defined, it is not assigned a value. Go will assign the default zero value to any fields that are not given values. For a string, the zero value is the empty string, which is why a blank space appears to the left of the 0 in the first line of the output.

  • One way to create a struct is to use a struct literal, as shown on line 15. When using a struct literal, you supply a comma-delimited list of the field names and the values they should be assigned.

  • When using a struct literal in this way, you do not need to specify all of the fields, as shown on line 18. Because the employeeID for heather was not defined, it takes on the zero value (for an integer, this is 0).

  • Lastly, you can also use a struct literal without listing the fields’ names, as shown on line 21. The values for the fields will be assigned according to the order that the fields are defined in the struct type definition. You must supply values for all of the fields in order to use this syntax.

    Note
    The mihalis variable is defined using the := syntax, which infers the Employee type for the variable from the assigned value.

Comparing Structs

Structs can be compared for equality. Two structs are equal if they have the same type and if their fields’ values are equal.

File: employee.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func main() {
    employee1 := Employee{"Heather", 1910234}
    employee2 := Employee{"Heather", 1910234}
    fmt.Println(employee1 == employee2)
}

The output of employee.go will be:

go run employee.go
true
Note
Structs cannot be ordered with operators like greater-than > or less-than <.

Accessing Fields

You can access a specific field using the struct variable name followed by a . character followed by the name of the field (also referred to as dot notation). Given an Employee variable named mihalis, the struct’s two fields can be individually accessed as mihalis.FirstName and mihalis.employeeID:

File: employee.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func main() {
    mihalis := Employee{"Mihalis", 1910234}
    fmt.Println("My name is", mihalis.FirstName, "and my employee ID is", mihalis.employeeID)
}

The output of employee.go will be:

go run employee.go
My name is Mihalis and my employee ID is 1910234

Public and Private Fields

In order to be able to use a struct and its fields outside of the Go package where the struct type is defined, both the struct name and the desired field names must begin with an uppercase letter. Therefore, if a struct has some field names that begin with a lowercase letter, then these particular fields will be private to the Go package that the struct is defined. This is a global Go rule that also applies to functions and variables.

To illustrate, consider these two Go files:

File: employee/employee.go
1
2
3
4
5
6
package employee

type Employee struct {
    FirstName string
    employeeID int
}
File: main.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import (
    "fmt"
    . "./employee"
)

func main() {
    mihalis := Employee{"Mihalis", 1910234}
    fmt.Println("My name is", mihalis.FirstName, "and my employee ID is", mihalis.employeeID)
}
Note
In this example, employee.go is created within an employee directory.

The output of main.go will be:

go run main.go
# command-line-arguments
./main.go:9:31: implicit assignment of unexported field 'employeeID' in employee.Employee literal
./main.go:10:80: mihalis.employeeID undefined (cannot refer to unexported field or method employeeID)

This error reflects the fact that employeeID has a lowercase name and is not an exported field of the Employee struct.

Value Semantics

By default, when a struct is assigned to a variable, it is copied. Consider this example:

File: employee.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func main() {
    employee1 := Employee{"Nathan", 8124011}
    fmt.Println("employee1:", employee1)
    employee2 := employee1
    employee2.FirstName = "Andy"
    employee2.employeeID = 1231410
    employee1.FirstName = "Nate"
    fmt.Println("employee1:", employee1)
    fmt.Println("employee2:", employee2)
}

The output of employee.go will be:

go run employee.go
employee1: {Nathan 8124011}
employee1: {Nate 8124011}
employee2: {Andy 1231410}

The employee2 := employee1 assignment creates a copy of employee1 and saves it in employee2. Changing the employee1 variable will not affect the contents of employee2 after the assignment.

Value Semantics with Functions

A struct can be passed to a function. By default, the struct will be copied to its function argument variable. Consider this example:

File: employee.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func ChangeEmployeeID(e Employee, newID int) {
    e.employeeID = newID
}

func main() {
    employee1 := Employee{"Nathan", 8124011}
    fmt.Println(employee1)
    ChangeEmployeeID(employee1, 1012843)
    fmt.Println(employee1)
}

The output of employee.go will be:

go run employee.go
{Nathan 8124011}
{Nathan 8124011}

Calling the ChangeEmployeeID function has no effect on the value of employee outside of the function scope. As a result, the output of the print statement on line 20 will be the same as the output of line 18’s print statement.

Pointers and Structs

As Go supports pointers, you can create pointers to structs. The use of pointer structs is illustrated in pointers.go.

File: pointers.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func main() {
    var employeePointer1 *Employee = &Employee{"Nathan", 1201921}
    fmt.Println("Getting a specific struct field:", (*employeePointer1).FirstName)
    fmt.Println("With implicit dereferencing:", employeePointer1.FirstName)

    employeePointer2 := employeePointer1
    employeePointer2.FirstName = "Nate"
    fmt.Println("FirstName for employeePointer2:", employeePointer2.FirstName)
    fmt.Println("FirstName for employeePointer1:", employeePointer1.FirstName)
}

The output of pointers.go will be:

go run pointers.go
Getting a specific struct field: Nathan
With implicit dereferencing: Nathan
FirstName for employeePointer2: Nate
FirstName for employeePointer1: Nate

employeePointer1 points to the memory location of the struct created with the struct literal on line 13. Inserting an ampersand (&) before the struct literal (e.g. Employee{"Nathan", 1201921}) indicates that the memory location for it should be assigned.

Line 14 shows how to dereference the pointer by inserting a * before the variable name, which tells Go to return the struct located at the memory location of your pointer. Surrounding this with parentheses and then using dot notation (e.g. (*employeePointer1).FirstName) allows you to access fields within the struct.

However, Go allows you to implicitly dereference a pointer to a struct in this circumstance. This means that you can simply use normal dot notation (e.g. employeePointer1.FirstName) to access fields, even if your struct variable is a pointer.

Lines 17-20 show that creating a second pointer to a struct allows you to manipulate that struct from another variable. In this case, the value of the FirstName field for employeePointer1 has been updated after it was assigned through employeePointer2 on line 18. This is in contrast with the value semantics demonstrated previously.

Pointers and Structs and Functions

Passing a pointer to a struct as an argument to a function will allow you to mutate that struct from inside the function scope. Consider this example:

File: pointers.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func ChangeEmployeeID(e *Employee, newID int) {
    e.employeeID = newID
}

func main() {
    employeePointer1 := &Employee{"Nathan", 8124011}
    fmt.Println(*employeePointer1)
    ChangeEmployeeID(employeePointer1, 1012843)
    fmt.Println(*employeePointer1)
}

The output of pointers.go will be:

go run pointers.go
{Nathan 8124011}
{Nathan 1012843}

Alternatively, using this code in the main function instead will produce identical results:

File: pointers.go
1
2
3
4
5
6
func main() {
    employee1 := Employee{"Nathan", 8124011}
    fmt.Println(employee1)
    ChangeEmployeeID(&employee1, 1012843)
    fmt.Println(employee1)
}

Methods

Go methods allow you to associate functions with structs. A method definition looks like other function definitions, but it also includes a receiver argument. The receiver argument is the struct that you wish to associate the method with.

Once defined, the method can be called using dot-notation on your struct variable. Here’s an example of what this looks like:

File: method.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func (e Employee) PrintGreeting() {
    fmt.Println("My name is", e.FirstName, "and my employee ID is", e.employeeID)
}

func main() {
    employee1 := Employee{"Nathan", 8124011}
    employee1.PrintGreeting()
}

The output of method.go will be:

go run method.go
My name is Nathan and my employee ID is 8124011

The receiver argument is listed in parentheses, prior to the function name, and has the syntax (variableName Type); see line 12 for an example of this.

Pointers and Methods

Using a pointer as the receiver type will allow you to mutate the pointed-to struct from within the method’s scope:

File: method.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func (e *Employee) ChangeEmployeeID(newID int) {
    e.employeeID = newID
}

func main() {
    var employeePointer1 *Employee = &Employee{"Nathan", 8124011}
    fmt.Println(*employeePointer1)
    employeePointer1.ChangeEmployeeID(1017193)
    fmt.Println(*employeePointer1)
}

The output of method.go will be:

go run method.go
{Nathan 8124011}
{Nathan 1017193}

You can also call a method with a pointer-type receiver on a normal non-pointer struct variable. Go will automatically convert the non-pointer struct variable to its memory location, and the struct will still be mutated within the function scope. This example will produce identical results to the one above:

File: method.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func (e *Employee) ChangeEmployeeID(newID int) {
    e.employeeID = newID
}

func main() {
    var employee1 Employee = Employee{"Nathan", 8124011}
    fmt.Println(employee1)
    employee1.ChangeEmployeeID(1017193)
    fmt.Println(employee1)
}

Creating Structs

In addition to the struct literal syntax used so far, there are a few other common ways to create a struct:

Constructor Functions

One common pattern for creating structs is with a “constructor” function. In Go, this is just a normal function that returns a struct, or a pointer to a struct. This example will demonstrate returning a pointer to a struct:

File: constructor.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
    "fmt"
)

type Employee struct {
    FirstName string
    employeeID int
}

func NewEmployee(name string, employeeID int) *Employee {
    if employeeID <= 0 {
        return nil
    }
    return &Employee{name, employeeID}
}

func main() {
    employeePointer1 := NewEmployee("Nathan", 8124011)
    fmt.Println(*employeePointer1)
}

This approach for creating new struct variables allows you to check whether the provided information is correct and valid in advance; for example, the above code checks the passed employeeID from lines 13 to 15. Additionally, with this approach you have a central point where struct fields are initialized, so if there is something wrong with your fields, you know exactly where to look.

Note
For those of you with a C or C++ background, it is perfectly legal for a Go function to return the memory address of a local variable. Nothing gets lost, so everybody is happy!

Using the new Keyword

Go supports the new keyword that allows you to allocate new objects with the following syntax:

1
variable := new(StructType)

new has these behaviors:

  • new returns the memory address of the allocated object. Put simply, new returns a pointer.

  • new allocates zeroed storage.

    Note
    Using new with a struct type is similar to assigning structType{} to a variable. In other words, t := new(Telephone) is equivalent to t := Telephone{}.

The following code example explores this behavior in more depth:

File: new.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
    "encoding/json"
    "fmt"
)

func prettyPrint(s interface{}) {
    p, _ := json.MarshalIndent(s, "", "\t")
    fmt.Println(string(p))
}

type Contact struct {
    Name string
    Main Telephone
    Tel  []Telephone
}

type Telephone struct {
    Mobile bool
    Number string
}

func main() {
    contact := new(Contact)
    telephone := new(Telephone)

    if contact.Main == (Telephone{}) {
        fmt.Println("contact.Main is an empty Telephone struct.")
    }
    fmt.Println("contact.Main")
    prettyPrint(contact.Main)

    if contact.Tel == nil {
        fmt.Println("contact.Tel is nil.")
    }

    fmt.Println("contact")
    prettyPrint(contact)
    fmt.Println("telephone")
    prettyPrint(telephone)
}
Note
The prettyPrint() function is just used for printing the contents of a struct in a readable and pleasant way with the help of the json.MarshalIndent() function.

Executing new.go will generate the following output:

go run new.go
contact.Main is an empty Telephone struct.
contact.Main
{
    "Mobile": false,
    "Number": ""
}
contact.Tel is nil.
contact
{
    "Name": "",
    "Main": {
        "Mobile": false,
        "Number": ""
    },
    "Tel": null
}
telephone
{
    "Mobile": false,
    "Number": ""
}
  • As Record.Tel is a slice, its zero value is nil. Lines 34-36 show that comparing it to nil returns true.
  • Record.Main is a Telephone struct, so it cannot be compared to nil – it can only be compared to Telephone{}, as demonstrated in lines 28-30.

Structs and JSON

Structs are really handy when we have to work with JSON data. This section is going to present a simple example where a struct is used for reading a text file that contains data in the JSON format and for creating data in the JSON format.

File: json.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type Record struct {
    Name    string
    Surname string
    Tel     []Telephone
}

type Telephone struct {
    Mobile bool
    Number string
}

func loadFromJSON(filename string, key interface{}) error {
    in, err := os.Open(filename)
    if err != nil {
        return err
    }

    decodeJSON := json.NewDecoder(in)
    err = decodeJSON.Decode(key)
    if err != nil {
        return err
    }
    in.Close()
    return nil
}

func saveToJSON(filename *os.File, key interface{}) {
    encodeJSON := json.NewEncoder(filename)
    err := encodeJSON.Encode(key)
    if err != nil {
        fmt.Println(err)
        return
    }
}

func main() {
    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Please provide a filename!")
        return
    }

    filename := arguments[1]

    var myRecord Record
    err := loadFromJSON(filename, &myRecord)
    fmt.Println("JSON file loaded into struct:")
    if err == nil {
        fmt.Println(myRecord)
    } else {
        fmt.Println(err)
    }

    myRecord = Record{
        Name:    "Mihalis",
        Surname: "Tsoukalos",
        Tel: []Telephone{Telephone{Mobile: true, Number: "1234-5678"},
            Telephone{Mobile: true, Number: "6789-abcd"},
            Telephone{Mobile: false, Number: "FAVA-5678"},
        },
    }

    fmt.Println("struct saved to JSON:")
    saveToJSON(os.Stdout, myRecord)
}
  • The loadFromJSON() function is used for decoding the data of a JSON file according to a data structure that is given as the second argument to it.
    • We first call json.NewDecoder() to create a new JSON decoder variable that is associated with a file.

    • We then call the Decode() function for actually decoding the contents of the file and putting them into the desired variable.

    • The function uses the empty interface type (interface{}) in order to be able to accept any data type.

      Note
      You will learn more about interfaces in a forthcoming guide.
  • The saveToJSON() function creates a JSON encoder variable named encodeJSON, which is associated with a filename, which is where the data is going to be put.
    • The call to Encode() is what puts the data into the desired file after encoding it.
    • In this example, saveToJSON() is called using os.Stdout, which means that data is going to standard output.
    • Last, the myRecord variable contains sample data using the Record and Telephone structs defined at the beginning of the program. It is the contents of the myRecord variable that are processed by saveToJSON().

Run the JSON Example

For the purposes of this section we are going to use a simple JSON file named record.json that has the following contents:

File: record.json
1
2
3
4
5
6
7
8
9
{
    "Name":"Mihalis",
    "Surname":"Tsoukalos",
    "Tel":[
        {"Mobile":true,"Number":"1234-567"},
        {"Mobile":true,"Number":"1234-abcd"},
        {"Mobile":false,"Number":"abcc-567"}
    ]
}

Executing json.go and processing the data found in record.json will generate the following output:

go run json.go record.json
{Mihalis Tsoukalos [{true 1234-567} {true 1234-abcd} {false abcc-567}]}
{"Name":"Mihalis","Surname":"Tsoukalos","Tel":[{"Mobile":true,"Number":"1234-5678"},{"Mobile":true,"Number":"6789-abcd"},{"Mobile":false,"Number":"FAVA-5678"}]}

Next Steps

Structs are a versatile Go data type because they allow you to create new types by combining existing data types. If you feel confident in the topics covered in this tutorial, try exploring our other guides on the Go language.

More Information

You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

This page was originally published on


Your Feedback Is Important

Let us know if this guide was helpful to you.


Join the conversation.
Read other comments or post your own below. Comments must be respectful, constructive, and relevant to the topic of the guide. Do not post external links or advertisements. Before posting, consider if your comment would be better addressed by contacting our Support team or asking on our Community Site.