One place for hosting & domains

      Structs

      Structs in Go – A Tutorial


      Updated by Linode Contributed by Mihalis Tsoukalos

      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:

      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.

      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:

      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.

      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:

      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:

      employee/employee.go
      1
      2
      3
      4
      5
      6
      
      package employee
      
      type Employee struct {
          FirstName string
          employeeID int
      }
      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:

      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:

      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.

      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:

      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 8124011}
      
      

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

      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:

      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:

      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:

      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:

      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:

      The following code example explores this behavior in more depth:

      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.

      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.

      • 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:

      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 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 guide is published under a CC BY-ND 4.0 license.



      Source link

      Defining Structs in Go


      Introduction

      Building abstractions around concrete details is the greatest tool that a programming language can give to a developer. Structs allow Go developers to describe the world in which a Go program operates. Instead of reasoning about strings describing a Street, City, or a PostalCode, structs allow us to instead talk about an Address. They serve as a natural nexus for documentation in our efforts to tell future developers (ourselves included) what data is important to our Go programs and how future code should use that data appropriately. Structs can be defined and used in a few different ways. In this tutorial, we’ll take a look at each of these techniques.

      Defining Structs

      Structs work like paper forms that you might use, for example, to file your taxes. Paper forms might have fields for textual pieces of information like your first and last names. Besides text fields, forms might have checkboxes to indicate Boolean values such as “married” or “single,” or date fields for birth date. Similarly, structs collect different pieces of data together and organize them under different field names. When you initialize a variable with a new struct, it’s as though you’ve photocopied a form and made it ready to fill out.

      To create a new struct, you must first give Go a blueprint that describes the fields the struct contains. This struct definition usually begins with the keyword type followed by the name of the struct. After this, use the struct keyword followed by a pair of braces {} where you declare the fields the struct will contain. Once you have defined the struct, you are then able to declare variables that use this struct definition. This example defines a struct and uses it:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy the Shark",
          }
          fmt.Println(c.Name)
      }
      

      When you run this code, you will see this output:

      output

      Sammy the Shark

      We first define a Creature struct in this example, containing a Name field of type string. Within the body of main, we create an instance of Creature by placing a pair of braces after the name of the type, Creature, and then specifying values for that instance’s fields. The instance in c will have its Name field set to “Sammy the Shark”. Within the fmt.Println function invocation, we retrieve the values of the instance’s field by placing a period after the variable where the instance was created, followed by the name of the field we would like to access. For example, c.Name in this case returns the Name field.

      When you declare a new instance of a struct, you generally enumerate the field names with their values, as in the last example. Alternatively, if every field value will be provided during the instantiation of a struct, you can omit the field names, like in this example:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      }
      
      func main() {
          c := Creature{"Sammy", "Shark"}
          fmt.Println(c.Name, "the", c.Type)
      }
      

      The output is the same as the last example:

      output

      Sammy the Shark

      We’ve added an extra field to Creature to track the Type of creature as a string. When instantiating Creature within the body of main, we’ve opted to use the shorter instantiation form by providing values for each field in order and omitting their field names. In the declaration Creature{"Sammy", "Shark"}, the Name field takes the value Sammy and the Type field takes the value Shark because Name appears first in the type declaration, followed by Type.

      This shorter declaration form has a few drawbacks that have led the Go community to prefer the longer form in most circumstances. You must provide values for each field in the struct when using the short declaration—you can’t skip fields you don’t care about. This quickly causes short declarations for structs with many fields to become confusing. For this reason, declaring structs using the short form is typically used with structs that have few fields.

      The field names in the examples so far have all begun with capital letters. This is more significant than a stylistic preference. The use of capital or lowercase letters for field names affects whether your field names will be accessible to code running in other packages.

      Struct Field Exporting

      Fields of a struct follow the same exporting rules as other identifiers within the Go programming language. If a field name begins with a capital letter, it will be readable and writeable by code outside of the package where the struct was defined. If the field begins with a lowercase letter, only code within that struct’s package will be able to read and write that field. This example defines fields that are exported and those that are not:

      package main
      
      import "fmt"
      
      type Creature struct {
          Name string
          Type string
      
          password string
      }
      
      func main() {
          c := Creature{
              Name: "Sammy",
              Type: "Shark",
      
              password: "secret",
          }
          fmt.Println(c.Name, "the", c.Type)
          fmt.Println("Password is", c.password)
      }
      

      This will output:

      output

      Sammy the Shark Password is secret

      We added an additional field to our previous examples, secret. secret is an unexported string field, which means that any other package that attempts to instantiate a Creature will not be able to access or set its secret field. Within the same package, we are able to access these fields, as this example has done. Since main is also in the main package, it’s able to reference c.password and retrieve the value stored there. It’s common to have unexported fields in structs with access to them mediated by exported methods.

      Inline Structs

      In addition to defining a new type to represent a struct, you can also define an inline struct. These on-the-fly struct definitions are useful in situations where inventing new names for struct types would be wasted effort. For example, tests often use a struct to define all the parameters that make up a particular test case. It would be cumbersome to come up with new names like CreatureNamePrintingTestCase when that struct is used in only one place.

      Inline struct definitions appear on the right-hand side of a variable assignment. You must provide an instantiation of them immediately after by providing an additional pair of braces with values for each of the fields you define. The example that follows shows an inline struct definition:

      package main
      
      import "fmt"
      
      func main() {
          c := struct {
              Name string
              Type string
          }{
              Name: "Sammy",
              Type: "Shark",
          }
          fmt.Println(c.Name, "the", c.Type)
      }
      

      The output from this example will be:

      output

      Sammy the Shark

      Rather than defining a new type describing our struct with the type keyword, this example defines an inline struct by placing the struct definition immediately following the short-assignment operator, :=. We define the fields of the struct as in previous examples, but then we must immediately supply another pair of braces and the values that each field will assume. Using this struct is now exactly the same as before—we can refer to field names using dot notation. The most common place you will see inline structs declared is during tests, as frequently one-off structs are defined to contain data and expectations for a particular test case.

      Conclusion

      Structs are collections of heterogenous data defined by programmers to organize information. Most programs deal with enormous volumes of data, and without structs, it would become difficult to remember which string or int variables belonged together or which were different. The next time that you find yourself juggling groups of variables, ask yourself if perhaps those variables would be better grouped together using a struct. Those variables may have been describing some higher-level concept all along.



      Source link