Composite literals in Go

Michał Łowicki
golangspec

--

Literals in source code allow to specify fixed values like numbers, strings or booleans. Go belongs to languages among f.ex. JavaScript or Python with ability to create using literals also compound types: arrays, maps, slices or structs. Golang’s simplicity and brevity reveals in composite literals too where single syntax is used for those types. In JavaScript it looks like:

var numbers = [1, 2, 3, 4]
var thing = {name: “Raspberry Pi”, generation: 2, model: “B”}

and quite similar in Python:

elements = [1, 2, 3, 4]
thing = {“name”: “Raspberry Pi”, “generation”: 2, “model”: “B”}

In Go it’s usually (there is some flexibility described above):

elements := []int{1, 2, 3, 4}
type Thing struct {
name string
generation int
model string
}
thing := Thing{“Raspberry Pi”, 2, “B”}
// or using explicit field names
thing = Thing{name: “Raspberry Pi”, generation: 2, model: “B”}

For every type except map, key is optional and interpreted without surprises:

  • for struct as field name
  • for array or slice as index

Key must be either literal constant or constant expression so it’s illegal to write:

f := func() int { return 1 }
elements := []string{0: “zero”, f(): “one”}

as it makes compiler angry — “index must be non-negative integer constant”. Since constant expressions or literals are valid then:

elements := []string{0: “zero”, 1: “one”, 4 / 2: “two”}

builds just fine.

Duplicate keys are forbidden:

elements := []string{
0: "zero",
1: "one",
4 / 2: "two",
2: "also two"
}

and outputs “duplicate index in array literal: 2” at compile time. The same applies to structs:

type S struct {
name string
}
s := S{name: “Michał”, name: “Michael”}

and results with “duplicate field name in struct literal: name” error.

Keys and values from composite literals must be assignable to respective keys, elements or struct fields. More on assignability in “Assignability in Go”.

Structs

Since struct types define fields, there’re couple of rules specific to creating instances of structs.

Definition of struct type enforces what names can be used and using something outside of such set generates compile-time error like “unknown S field ‘name’ in struct literal” for snippet:

type S struct {
age int8
}
s := S{name: “Michał”}

If at least one element of literal has key then all of them need to have it so impossible to write:

type S struct {
name string
age int8
}
s := S{name: “Michał”, 29}

as it throws an error “mixture of field:value and value initializers”. It’s possible though to omit keys for every element:

s := S{“Michał”, 29}

with additional constraint that order of fields in literal is the same as in declaration of struct type.

Requirement that field:value and value initializers cannot be mixed doesn’t mean we’ve to specify each field. Omitted fields will be set to zero value of field’s type:

type S struct {
name string
age int8
}
s := S{name: "Michał"}
fmt.Printf("%#v\n", s)

outputs:

main.S{name:"Michał", age:0}

Assigning zero values works only when key:value initializers are used so:

s := S{“Michał”}

cannot be built — “too few values in struct initializer”. This way programmer is safer when new field would be added to struct before the one specified in literal — if to struct above field “title string” would be added before name field then value “Michał” would be a title which could lead to hard to debug issues.

If struct literal is empty then it’s evaluated to struct with each field zeroed:

type Employee struct {
department string
position string
}
type S struct {
name string
age int8
Employee
}
main.S{name:"", age:0, Employee:main.Employee{department:"", position:""}}

Last rule specific to struct is related to exported identifiers (in short it doesn’t allow to specify in literal non-exported field name).

Arrays & slices

Elements of slice or array are indexed so key from literal must be constant integer expression. For elements without key, the index of previous element plus one is used. Key (index) of first element from literal is zero if not specified.

numbers := []string{"a", "b", 2 << 1: "c", "d"}
fmt.Printf(“%#v\n”, numbers)
[]string{"a", "b", "", "", "c", "d"}

It’s valid to specify less element that length of an array (zero values will be used to fill missing spots):

fmt.Printf(“%#v\n”, [3]string{“foo”, “bar”})[3]string{“foo”, “bar”, “”}

It’s forbidden though to set value for out-of-range indices so all below lines are invalid:

[1]string{“foo”, “bar”}
[2]string{1: “foo”, “bar”}

There is an convenient notation using three dots (…) which frees programmer from specifying length of array and it’s computed by compiler by adding one to maximum index:

elements := […]string{2: “foo”, 4: “bar”}
fmt.Printf(“%#v, length=%d\n”, elements, len(elements))

outputs:

[5]string{“”, “”, “foo”, “”, “bar”}, length=5

Slice literal specifies the whole underlaying array:

els := []string{2: “foo”, 4: “bar”}
fmt.Printf(“%#v, length=%d, capacity=%d\n”, els, len(els), cap(els))

and computes to:

[]string{“”, “”, “foo”, “”, “bar”}, length=5, capacity=5

Maps

Syntax for map literals is very similar to arrays but length is replaced with type of key and there is a reserved key map at the beginning:

constants := map[string]float64{“euler”: 2.71828, “pi”: .1415926535}

Shortcut

If type of literal used as map key or element of array, slice or map is identical to type of key or element then such type can be omitted for brevity:

coords := map[[2]byte]string{{1, 1}: “one one”, {2, 1}: “two one”}type Engineer struct {
name string
age byte
}
engineers := [...]Engineer{{"Michał", 29}, {"John", 25}}

In the same way &T can be skipped if key or element is a pointer type:

engineers := […]*Engineer{{“Michał”, 29}, {“John”, 25}}
fmt.Printf(“%#v\n”, engineers)

outputs:

[2]*main.Engineer{(*main.Engineer)(0x8201cc1e0), (*main.Engineer)(0x8201cc200)}

Resources:

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.