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
.
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)
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.
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.
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:
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
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.
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.
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.
3.137.186.178