What a field is
An empty struct type is a shell: every value of it is identical. A field is a variable that lives inside a struct, and every value of the struct type gets its own copy of each field. Fields are how a Dog knows its name and age.
Declaring fields
type Dog struct {
name string
age int
}
Each field is its name followed by its type, one per line. There is no var keyword and no commas between fields. Fields of the same type can share a line, as in x, y int, but one per line reads better.
The first letter of a field name controls its visibility. A capitalised name such as Name is exported and usable from other packages. A lowercase name such as name is visible only inside its own package. In these lessons everything sits in one package, so lowercase fields can be used freely.
Reading and writing with the dot operator
dog := Dog{}
dog.name = "Rex" // write
dog.age = 4
fmt.Println(dog.name) // read
The dot operator reaches a field of one particular value: dog.age is the age of this dog. Two dogs have separate fields, so changing one does not touch the other.
Zero values
A field you never set is not garbage and not an error. It holds the zero value of its type, which is "" for string, 0 for numbers and false for bool. The line dog := Dog{} therefore gives a dog with an empty name and age 0, and printing it with %+v shows {name: age:0}. This is why Go has no "uninitialised field" bugs of the kind found in some other languages, though an empty name can still be a logic bug.
You can also set fields at creation with a literal that names them: Dog{name: "Rex", age: 4}. The next lesson, on constructors, builds on that.
Fields at work: two dogs
A struct with fields is also the usual way to pass related data around together. Instead of three separate variables for a dog's name, age and owner, one Dog value carries all three through function calls and into slices or maps.
Because each value owns its fields, a program can keep several dogs side by side and change them independently:
rex := Dog{name: "Rex", age: 4}
luna := Dog{name: "Luna", age: 7}
luna.age++
fmt.Println(rex.age, luna.age) // 4 8
Fields can have any type, including slices, maps, other structs and pointers. A field of struct type is reached by chaining dots, such as dog.owner.name. Two struct values whose fields are all comparable can be compared with ==, which is true when every field is equal.
The lesson's exercise
The Dog struct is empty and the starter code in mainFunction sets the age and prints the dog. Three edits complete it.
- Add the fields inside the struct:
nameas astringandageas anint. - Declare
dogat the top ofmainFunctionwithdog := Dog{}. Both fields start at their zero values. - Set the name with
dog.name = "Rex". The age line is already in place.
type Dog struct {
name string
age int
}
func mainFunction() {
dog := Dog{}
dog.name = "Rex"
dog.age = 4
fmt.Printf("%s is %d years old\n", dog.name, dog.age)
}
The program prints:
Rex is 4 years old
Common mistakes
- Writing
varor commas inside the struct.var name string,is a syntax error. Writename string. - Mixing up the order. Go puts the type after the name:
age int, notint age. - Assigning the wrong type.
dog.age = "four"does not compile, becauseageis anint. - Assuming a copy shares fields.
other := dogcopies every field, so changingother.nameafterwards does not changedog.name. - Accessing a lowercase field from another package. That is a compile error. Capitalise the field, or give the type a method that exposes it.
Key takeaways
- Fields are declared inside
type Name struct { ... }as name then type. - Each struct value has its own copy of every field.
- The dot operator reads and writes a field.
- Unset fields hold the zero value of their type.
- Capitalised field names are exported, and lowercase ones are package-private.