The struct type

The last type discussed in this chapter is Go's struct. It is a composite type that serves as a container for other named types known as fields. The following code snippet shows several variables declared as structs:

var( 
   empty struct{} 
   car struct{make, model string} 
   currency struct{name, country string; code int} 
   node struct{ 
         edges []string 
         weight int 
   } 
   person struct{ 
         name string 
         address struct{ 
               street string 
               city, state string 
               postal string 
         } 
   } 
) 

golang.fyi/ch07/structtypes.go

Note that the struct type has the following general format:

struct{<field declaration set>}

The struct type is constructed by specifying the keyword struct followed by a set of field declarations enclosed within curly brackets. In its most common form, a field is a unique identifier with an assigned type which follows Go's variable declaration conventions as shown in the previous code snippet (struct also support anonymous fields, covered later).

It is crucial to understand that the type definition for a struct includes all of its declared fields. For instance, the type for the person variable (see earlier code snippet) is the entire set of fields in the declaration struct { name string; address struct { street string; city string; state string; postal string }}. Therefore, any variable or expression requiring that type must repeat that long declaration. We will see later how that is mitigated by using named types for struct.

Accessing struct fields

A struct uses a selector expression (or dot notation) to access the values stored in fields. For instance, the following would print the value of the name field of the person struct variable from the previous code snippet:

fmt.Pritnln(person.name)

Selectors can be chained to access fields that are nested inside a struct. The following snippet would print the street and city for the nested address value of a person variable:

fmt.Pritnln(person.address.street)
fmt.Pritnln(person.address.city)

Struct initialization

Similar to arrays, structs are pure values with no additional underlying storage structure. The fields for an uninitialized struct are assigned their respective zero values. This means an uninitialized struct requires no further allocation and is ready to be used.

Nevertheless, a struct variable can be explicitly initialized using a composite literal of the following form:

<struct_type>{<positional or named field values>}

The composite literal value for a struct can be initialized by a set of field values specified by their respective positions. Using this approach, all field values must be provided, to match their respective declared types, as shown in the following snippet:

var( 
   currency = struct{ 
         name, country string 
         code int 
   }{ 
         "USD", "United States",  
         840, 
   } 
... 
) 

golang.fyi/ch07/structinit.go

In the previous struct literal, all field values of the struct are provided, matching their declared field types. Alternatively, the composite literal value of a struct can be specified using a field indices and their associated value. As before, the index (the field name) and its value is separated by a colon, as shown in the following snippet:

var( 
   car = struct{make, model string}{make:"Ford", model:"F150"} 
   node = struct{ 
         edges []string 
         weight int 
   }{ 
         edges: []string{"north", "south", "west"}, 
   } 
... 
) 

golang.fyi/ch07/structinit.go

As you can see, field values of the composite literal can be selectively specified when the index and its value are provided. For instance, in the initialization of the node variable, the edge field is initialized while weight is omitted.

Declaring named struct types

Attempting to reuse struct types can get unwieldy fast. For instance, having to write struct { name string; address struct { street string; city string; state string; postal string }} to express a struct type, every time it is needed, would not scale, would be error prone, and would make for grumpy Go developers. Luckily, the proper idiom to fix this is to use named types, as illustrated in the following source code snippet:

type person struct { 
   name    string 
   address address 
} 
 
type address struct { 
   street      string 
   city, state string 
   postal      string 
} 
 
func makePerson() person { 
   addr := address{ 
         city: "Goville", 
         state: "Go", 
         postal: "12345", 
   } 
   return person{ 
         name: "vladimir vivien", 
         address: addr, 
   } 
} 

golang.fyi/ch07/structtype_dec.go

The previous example binds struct type definitions to the identifiers person and address. This allows the struct types to be reused in different contexts without the need to carry around the long form of the type definitions. You can refer to Chapter 4, Data Types, to learn more about named types.

The anonymous field

Previous definitions of struct types involved the use of named fields. However, it is also possible to define a field with only its type, omitting the identifier. This is known as an anonymous field. It has the effect of embedding the type directly into the struct.

This concept is demonstrated in the following code snippet. Both types, diameter and the name, are embedded as anonymous fields in the planet type:

type diameter int 
 
type name struct { 
   long   string 
   short  string 
   symbol rune 
} 
    
type planet struct { 
   diameter 
   name 
   desc string 
} 
 
func main() { 
   earth := planet{ 
         diameter: 7926, 
         name: name{ 
               long:   "Earth", 
               short:  "E", 
               symbol: 'u2641', 
         }, 
         desc: "Third rock from the Sun", 
   } 
   ... 
} 

golang.fyi/ch07/struct_embed.go

The main function in the previous snippet shows how the anonymous fields are accessed and updated, as is done in the planet struct. Notice the names of the embedded types become the field identifiers in the composite literal value for the struct.

To simplify field name resolution, Go follows the following rules when using anonymous fields:

  • The name of the type becomes the name of the field
  • The name of an anonymous field may not clash with other field names
  • Use only the unqualified (omit package) type name of imported types

These rules also hold when accessing the fields of embedded structs directly using selector expressions, as is shown in the following code snippet. Notice the name of the embedded types are resolved as fields names:

func main(){ 
   jupiter := planet{} 
   jupiter.diameter = 88846 
   jupiter.name.long = "Jupiter" 
   jupiter.name.short = "J" 
   jupiter.name.symbol = 'u2643' 
   jupiter.desc = "A ball of gas" 
   ... 
} 

golang.fyi/ch07/struct_embed.go

Promoted fields

Fields of an embedded struct can be promoted to its enclosing type. Promoted fields appear in selector expressions without the qualified name of their types, as shown in the following example:

func main() {
...
saturn := planet{}
saturn.diameter = 120536
saturn.long = "Saturn"
saturn.short = "S"
saturn.symbol = 'u2644'
saturn.desc = "Slow mover"
...
}

golang.fyi/ch07/struct_embed.go

In the previous snippet, the highlighted fields are promoted from the embedded type name by omitting it from the selector expression. The values of the fields long, short, and symbol come from embedded type name. Again, this will only work if the promotion does not cause any identifier clashes. In case of ambiguity, the fully qualified selector expression can be used.

Structs as parameters

Recall that struct variables store actual values. This implies that a new copy of a struct value is created whenever a struct variable is reassigned or passed in as a function parameter. For instance, the following will not update the value of name after the call to updateName():

type person struct { 
   name    string 
   title string       
} 
func updateName(p person, name string) { 
   p.name = name 
}  
 
func main() { 
   p := person{} 
   p.name = "uknown" 
   ... 
   updateName(p, "Vladimir Vivien") 
} 

golang.fyi/ch07/struct_ptr.go

This can be remedied by passing a pointer to the struct value of the person type, as shown in the following snippet:

type person struct { 
   name    string 
   title string 
} 
 
func updateName(p *person, name string) { 
   p.name = name 
} 
 
func main() { 
   p := new(person) 
   p.name = "uknown" 
   ... 
   updateName(p, "Vladimir Vivien") 
} 

golang.fyi/ch07/struct_ptr2.go

In this version, the p variable is declared as *person and is initialized using the built-in new() function. After updateName() returns, its changes are seen by the calling function.

Field tags

The last topic on structs has to do with field tags. During the definition of a struct type, optional string values may be added to each field declaration. The value of the string is arbitrary and it can serve as hints to tools or other APIs that use reflection to consume the tags.

The following shows a definition of the Person and Address structs that are tagged with JSON annotation which can be interpreted by Go's JSON encoder and decoder (found in the standard library):

type Person struct { 
   Name    string `json:"person_name"` 
   Title   string `json:"person_title"` 
   Address `json:"person_address_obj"` 
} 
 
type Address struct { 
   Street string `json:"person_addr_street"` 
   City   string `json:"person_city"` 
   State  string `json:"person_state"` 
   Postal string `json:"person_postal_code"` 
} 
func main() { 
   p := Person{ 
         Name: "Vladimir Vivien", 
         Title : "Author", 
         ... 
   } 
   ... 
   b, _ := json.Marshal(p) 
   fmt.Println(string(b)) 
} 

golang.fyi/ch07/struct_ptr2.go

Notice the tags are represented as raw string values (wrapped within a pair of ``). The tags are ignored by normal code execution. However, they can be collected using Go's reflection API as is done by the JSON library. You will encounter more on this subject in Chapter 10, Data IO in Go, when the book discusses input and output streams.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.186.178