Once a struct type gets defined, there are two ways to build struct values from it. One is mapping. See Mapping Structs. The other is using a struct constructor, which is explained in this section.
Struct constructors have the following form:
type_name { [field_initializer,]… }
where each field_initializer has the form:
[field_name=]exp
Note how each field has an optional name field_name and a value exp. The expression for the value can be of any type. For convenience, a trailing comma is accepted but not required.
Suppose for example that we have a type defined as:
type Packet = struct { uint<16> flags; byte[32] data; }
We can construct a new packet, with all its fields initialized to zero, like this:
(poke) Packet {} Packet { flags=0x0UH, data=[0x0UB,0x0UB,0x0UB,0x0UB,0x0UB,…] }
In the constructor we can specify initial values for some of the fields:
(poke) Packet { flags = 0x8 } Packet { flags=0x8UH, data=[0x0UB,0x0UB,0x0UB,0x0UB,0x0UB,…] }
It is not allowed to specify initializers that are not part of the type being constructed:
(poke) Packet { foo = 10 } <stdin>:1:10: error: invalid struct field `foo' in constructor Packet { foo = 10 }; ^~~
As we shall see later, many struct types define constraints on the values their fields can hold. This is a way to implement data integrity. While building struct values using constructors, these constraints are taken into account. For example, given the following struct type:
type BPF_Reg = struct { uint<4> code : code < 11; };
we will get a constraint violation exception if we try to construct an
BPF_Reg
:
(poke) BPF_Reg { code = 20 } unhandled constraint violation exception