Go Structs
External
Internal
Overview
Structs are user-defined composite types, grouping together instances of arbitrary types, into one object.
A struct declaration consists in a sequence of named elements, called fields. Each field has one or more names and a type. The name(s) can be explicitly listed in the structure, or can be implicit, for embedded fields. A field may optionally have a tag. More details about fields can be found in the Fields section.
A struct variable is a value, not a reference variable, which means that no two different struct variables may point to the same struct instance.
Structs can represent data values that could have either a primitive or non-primitive value. "Primitive" structs imply creating a new instance every time an existing value is mutated. In this case, values themselves, rather than pointers, are used to share values of those structs. An example is the Time
structure in the time
package. However, in most cases, structs exhibit a non-primitive structure: adding or removing something from the value mutates the value, does not create a new value.
Printing Structs
To print the abbreviated struct, and only display values, and not field names, use:
fmt.Println(s)
To print both field names and values, use the %+v
format specifier:
fmt.Printf("%+v\n", s)
Declaration
Struct types can be declared at package level or inside a function.
The struct type definition is introduced by the type
keyword, to indicate that this is a user-defined type, followed by the new type name and the keyword struct
, followed by a curly-brackets-enclosed enumeration of fields.
Once the struct type has been defined in a package or a function, variables of that type can be declared using the long declaration:
var i Item
If no explicit initialization follows, the struct variables declared as above have all their fields initialized with [[Go_Language#Zero_Value|zero values]. Short declaration can also be used, as shown in the Initialization section, below.
Fields
Each field has one or more names and a type. The type can be a pre-declared type or a user-defined type, such as another struct
or an interface. No comma is required at the end of the field line. The name(s) can be explicitly listed in the structure, or can be implicit, in case of embedded fields. A field may optionally have a tag. Within a struct, non-blank field names must be unique.
A named field usually represents a has-a relationship, consistent with the struct's composite type nature, and an embedded field represents an is-a relationship (see below).
Fields can be exported outside the package declaring the type. For more details see the Exporting Fields section.
These are named fields:
type Item struct {
color string
size int
}
A field with multiple names can be declared as such:
type Item struct {
a, b, c int
}
We say that fields with the same types can be collapsed. This structure is fully equivalent with the structure where each of the names is declared on its own line:
type Item struct {
a int
b int
c int
}
Embedded Fields
When only the type but not the name of a filed is declared, the name of the field is implicitly the unqualified name of the type, and the field is called an embedded field or an anonymous field:
type SomeInterface interface {
...
}
type SomeOtherStruct struct {
s string
}
type Item struct {
SomeInterface
SomeOtherStruct
int
}
Embedded fields, unlike named fields, model an is-a relationship.
An embedded field can be a type name T
, a pointer to a non-interface type name (*T
), and T
itself may not be a pointer type. As mentioned above, the unqualified type name acts as the field name. The previous example declares two embedded fields, one of type int
and the other of type SomeInterface
, which is an interface declared somewhere in the package. The type renders as:
{SomeInterface:<nil> SomeOtherStruct:{s:} int:0}
Note that if an embedded field is specified using the package and the type name (the qualified type name), the implicit field name is the unqualified type name.
Embedded Field Promotion
The embedded field's type identifiers are promoted to the embedding type, so they can be accessed as it would belong to the other type. Promotion also applies to methods associated with the embedded type. A method associated with the embedded type works with the embedding type. This strengthens the point that embedded fields model an "is-a" relationship, and elevates the field embedding mechanism to a sort of inheritance mechanism. Type embedding is the Go's "extends". It allows types to extend, and it changes their behavior.
Field promotion:
type Animal struct {
name string
}
type Dog struct {
Animal
}
dog := &Dog{Animal{"Fido"}}
...
fmt.Printf("name: %s\n", dog.name) // the "name" field is promoted into the Dog structure
Method promotion, for the same structs Animal
and Dog
declared above:
func (a *Animal) Move() {
fmt.Printf("%s moves\n", a.name)
}
...
dog.Move() // Displays "Fido moves"
In the example above, the invocations dog.Move()
and dog.Animal.Move()
are equivalent.
If an interface is implemented by the embedded type, it is promoted to the embedding type.
Also see:
Blank Fields
type Item struct {
_ float32 // a blank field
_ int // another blank field
}
Promoted Fields
TODO: https://go.dev/ref/spec#Struct_types
Tags
A field declaration may be followed by an optional string literal tag, which is a string literal declared between `...`
(backticks). A tag becomes an attribute for all the fields in the corresponding field declaration. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.
type A struct {
Name string `json:"name"`
}
In the example above, a tag has been included to provide metadata the JSON decoding function needs to parse JSON content. Each tag maps a field name in the struct to a filed name in the JSON document. Tags are useful in JSON and YAML serialization/deserialization.
struct Zero Value
Expand this, struct
zero value is important when parsing YAML and entire subtrees are missing.
Empty struct
An empty struct allocates zero bytes when values of this type are created. They are useful when a type, but not state, is needed. Example:
// we declared an empty struct that defines the type "A"
type A struct{}
a := A{}
b := struct{}{}
Idiomatic Struct Naming Conventions
Since references include package names, variable names should not be prefixed with the package name. If the package name is xml
, use the name Reader
, not XMLReader
. In the second case, the full name would be xml.XMLReader
.
Also see:
Literal
Struct literals can have all field initialization values on the same line, or on different lines:
i := Item{color: "blue", size: 5}
i := Item {
color: "blue",
size: 5, // mandatory comma
}
There is "short" literal where the name of the fields are omitted, provided that the order in which they are declared is maintained:
i := Item {"blue", 5}
Embedded Literals
When a field of a struct is another struct, embedded literals can be used in initialization:
i := Item {
color: "blue",
packaging: {
material: "plastic",
protectionLevel: 10,
}
size: 5,
}
Initialization
Initialize a struct variable with an empty struct using the new()
built-in function and the short variable declaration:
i := new(Item)
This results in an "empty" structure, with all fields initialized to zero. Note new()
returns a pointer to the structure and not the structure itself.
A struct literal can also be used:
i := Item{color: "blue", size: 5}
Initializing Struct Fields to Something Else than Zero Value
It seems not to be possible. If I have a Set structure that internally contains a map, I have two options:
1. Use a NewSet() constructor that initializes the map.
2. Do lazy initialization on operations.
Operators
The Dot Operator
Individual fields in a struct can be read and modified using the "dot notation", the .
operator.
var i Item
i.color = "blue"
i.size = 2
fmt.Printf("color %s, size %d\b", i.color, i.size)
Note that the .
operator works with a regular struct variable as well as with a pointer to the struct. The compiler knows how to compensate for both of these cases:
i := new(Item)
fmt.Printf("i: %p\n", i) // i is a pointer
i.color = "blue" // the dot operator is applied to a pointer
i2 := Item{"red", 1}
fmt.Printf("i2: %+v\n", i2) // i2 is a struct
i2.color = "yellow" // the dot operator is applied to a struct
Exporting Structs
"Exporting Structs" section needs refactoring.
Even if the enclosing struct type is exported by a package, not all fields are exported automatically, only those whose first character is an upper case letter. This behavior makes possible to have "private" fields in a public structure, when the structure is used outside its package.
Encapsulation and Private Fields
Formally define the semantics of fields that start with lower case names. They are "hidden". What exactly does that mean? Apparently, other package cannot see them.
Aslo see:
Exporting Fields
The fields of an exported struct type can be exported or unexported on a field-by-field basis, by naming them with an uppercase and respectively lowercase letter.
What if the struct is named with a lowercase letter and the field starts with an uppercase letter?
Explain this behavior: YAML_in_Go#TODO_Ee4.
Exporting Embedded Fields
If the name of an embedded field starts with a lower case, it is unexported even if its outer type is exported. Even if the name of the inner type is not exported, its fields may be individually exported, and thus accessible from outside the package.