Structs

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 only allow certain keys. All of the values in a map must also 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.

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 like we put the variable name first and the variable type second in a var declaration, we put the struct field name first and the struct field type second.

Also note that unlike map literals, there are no commas separating 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 only be used within the function. Technically, you can scope a struct definition to any block level.

Once a struct type is declared, we can define variables of that type.

type person struct { name string age int pet string } var fred person println(fred)

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.

There are two different styles for a non-empty struct literal.

A struct literal can be specified as a comma-separated list of values for the fields inside of braces:

type person struct { name string age int pet string } tsb := person{} println(tsb) julia := person{ "Julia", 40, "cat", } println(julia)

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:

type person struct { name string age int pet string } beth := person{ age: 30, name: "Beth", } println(beth)

You use the names of the fields in the struct to specify the values. When you use this style, you can leave out keys and specify the fields in any order. Any field not specified is set to its zero value. You cannot mix the two struct literal styles; either all of the fields are specified with keys, 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 key 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 dotted notation:

type person struct { name string age int pet string } bob := person{ age: 30, name: "Beth", } bob.name = "Bob" println(bob.name)

Just like we use brackets for both reading and writing to a map, we use dotted notation for reading and writing struct fields.

Anonymous Structs

You can also declare that a variable implements 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" println(person) pet := struct { name string kind string }{ name: "Fido", kind: "dog", } println(pet)

In this example, the types of the variables person and pet are anonymous structs. You assign (and read) fields in an anonymous struct just like you do for a named struct type. Just like 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 only associated with a single instance. There are two common situations where anonymous structs are handy. 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 marshaling and unmarshaling data.

Writing tests is another place where anonymous structs pop up.

Comparing and Converting Structs

Whether or not a struct is comparable depends on the struct’s fields. Structs that are entirely composed out of comparable types are comparable; those with slice or map fields are not (as we will see in later chapters, function and channel fields also prevent a struct from being comparable).

Unlike Python or Ruby, 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 like 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 } type secondPerson struct { name string age int } type thirdPerson struct { age int name string } first := firstPerson{name: "Bob", age: 22} second := secondPerson{name: "Joe", age: 23} third := thirdPerson{name: "Sars", age: 24} first = firstPerson(second) println first first = firstPerson(third) println first

We can use a type conversion to convert an instance of secondPerson to firstPerson, but we can’t convert an instance of thirdPerson to firstPerson, because the fields are in a different order. But we can’t use == to compare an instance of firstPerson and an instance of secondPerson or thirdPerson, because they are different types.

Anonymous structs add a small twist to this: if two struct variables are being compared and at least one of them 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 } g = f println f == g

Next example: Pointers