Maps are a convenient way to store some kinds of data, but they have limitations. They don’t define an API since there’s no way to constrain a map to allow only certain keys. Also, all values in a map must be of the same type. For these reasons, maps are not an ideal way to pass data from function to function. When you have related data that you want to group together, you should define a struct.
Note
If you already know an object-oriented language, you might be wondering about the difference between classes and structs. The difference is simple: Go doesn’t have classes, because it doesn’t have inheritance. This doesn’t mean that Go doesn’t have some of the features of object-oriented languages it just does things a little differently. You’ll learn more about the object-oriented features of Go in the related discussion.
Most languages have a concept that’s similar to a struct, and the syntax that Go uses to read and write structs should look familiar:
type person struct {
name string
age int
pet string
}
A struct type is defined with the keyword type, the name of the struct type, the keyword struct, and a pair of braces ({}). Within the braces, you list the fields in the struct. Just as you put the variable name first and the variable type second in a var declaration, you put the struct field name first and the struct field type second. Also note that unlike in map literals, no commas separate the fields in a struct declaration. You can define a struct type inside or outside of a function. A struct type that’s defined within a function can be used only within that function.
Note
Technically, you can scope a struct definition to any block level. You’ll learn more about blocks in the related discussion.
Once a struct type is declared, you can define variables of that type:
var fred person
Here we are using a var declaration. Since no value is assigned to fred, it gets the zero value for the person struct type. A zero value struct has every field set to the field’s zero value.
A struct literal can be assigned to a variable as well:
bob := person{}
Unlike maps, there is no difference between assigning an empty struct literal and not assigning a value at all. Both initialize all fields in the struct to their zero values. There are two styles for a nonempty struct literal. First, a struct literal can be specified as a comma-separated list of values for the fields inside of braces:
julia := person{
"Julia",
40,
"cat",
}
When using this struct literal format, a value for every field in the struct must be specified, and the values are assigned to the fields in the order they were declared in the struct definition.
The second struct literal style looks like the map literal style:
beth := person{
age: 30,
name: "Beth",
}
You use the names of the fields in the struct to specify the values. This style has some advantages. It allows you to specify the fields in any order, and you don’t need to provide a value for all fields. Any field not specified is set to its zero value.
You cannot mix the two struct literal styles: either all fields are specified with names, or none of them are. For small structs where all fields are always specified, the simpler struct literal style is fine. In other cases, use the names. It’s more verbose, but it makes clear what value is being assigned to what field without having to reference the struct definition. It’s also more maintainable. If you initialize a struct without using the field names and a future version of the struct adds additional fields, your code will no longer compile.
A field in a struct is accessed with dot notation:
bob.name = "Bob"
fmt.Println(bob.name)
Just as you use brackets for both reading and writing to a map, you use dotted notation for reading and writing to struct fields.
Anonymous Structs
You can also declare that a variable has a struct type without first giving the struct type a name. This is called an anonymous struct:
var person struct {
name string
age int
pet string
}
person.name = "bob"
person.age = 50
person.pet = "dog"
pet := struct {
name string
kind string
}{
name: "Fido",
kind: "dog",
}
In this example, the types of the variables person and pet are anonymous structs. You assign (and read) fields in an anonymous struct just as you do for a named struct type. Just as you can initialize an instance of a named struct with a struct literal, you can do the same for an anonymous struct as well.
You might wonder when it’s useful to have a data type that’s associated only with a single instance. Anonymous structs are handy in two common situations. The first is when you translate external data into a struct or a struct into external data (like JSON or Protocol Buffers). This is called unmarshaling and marshaling data, respectively. You’ll learn how to do this in the related discussion>.
Writing tests is another place where anonymous structs pop up. You’ll use a slice of anonymous structs when writing table-driven tests in the related discussion.
Comparing and Converting Structs
Whether a struct is comparable depends on the struct’s fields. Structs that are entirely composed of comparable types are comparable; those with slice or map fields are not (as you will see in later chapters, function and channel fields also prevent a struct from being comparable).
Unlike in Python or Ruby, in Go there’s no magic method that can be overridden to redefine equality and make == and != work for incomparable structs. You can, of course, write your own function that you use to compare structs.
Just as Go doesn’t allow comparisons between variables of different primitive types, Go doesn’t allow comparisons between variables that represent structs of different types. Go does allow you to perform a type conversion from one struct type to another if the fields of both structs have the same names, order, and types. Let’s see what this means. Given this struct:
type firstPerson struct {
name string
age int
}
you can use a type conversion to convert an instance of firstPerson to secondPerson, but you can’t use == to compare an instance of firstPerson and an instance of secondPerson, because they are different types:
type secondPerson struct {
name string
age int
}
You can’t convert an instance of firstPerson to thirdPerson, because the fields are in a different order:
type thirdPerson struct {
age int
name string
}
You can’t convert an instance of firstPerson to fourthPerson because the field names don’t match:
type fourthPerson struct {
firstName string
age int
}
Finally, you can’t convert an instance of firstPerson to fifthPerson because there’s an additional field:
type fifthPerson struct {
name string
age int
favoriteColor string
}
Anonymous structs add a small twist: if two struct variables are being compared and at least one has a type that’s an anonymous struct, you can compare them without a type conversion if the fields of both structs have the same names, order, and types. You can also assign between named and anonymous struct types if the fields of both structs have the same names, order, and types:
type firstPerson struct {
name string
age int
}
f := firstPerson{
name: "Bob",
age: 50,
}
var g struct {
name string
age int
}
// compiles -- can use = and == between identical named and anonymous structs
g = f
fmt.Println(f == g)