21.5.2 Struct Constructors

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