Types and fields
1. Primitive types
| Type | Definition | Value range |
|---|---|---|
|
8-bit signed integer |
-128 … 127 |
|
8-bit unsigned integer |
0 … 255 |
|
16-bit signed integer |
-32 768 … 32 767 |
|
16-bit unsigned integer |
0 … 65 535 |
|
32-bit signed integer |
-231 … 231-1 |
|
32-bit unsigned integer |
0 … 232-1 |
|
64-bit signed integer |
-263 … 263-1 |
|
64-bit unsigned integer |
0 … 264-1 |
|
boolean |
|
|
IEEE-754 double-precision floating point |
— |
|
character string (text, by convention UTF-8) |
— |
|
binary blob |
— |
|
string carrying an XML fragment (affects only the XML representation) |
— |
|
no data; indicates a presence without an associated value |
Field types can also be any named type: a struct, union, class, enum or typedef, possibly qualified with its package name (Packages).
2. Field declarations
Fields of structs, unions and classes share one declaration syntax:
[<tag>:] <type>[? | [] | &] <name> [= <default>] ;
<name> starts with a lowercase letter and contains no underscore (unless
the field carries @forceFieldName). Two fields of the same structure can
share neither their name (field name 'x' is already in use) nor their
tag (tag <n> is used twice).
2.1. Cardinality
Every field has one of four cardinalities:
- Mandatory (required)
-
The default. The field must be present for an instance to be valid.
struct Foo { int mandatoryInteger; MyStruct mandatoryStruct; }; - Optional —
? -
The field may be absent. Codecs accept its absence without error.
struct Foo { int? optionalMember; Bar? optionalStruct; }; - Repeated —
[] -
The field is a list of zero or more values. An absent repeated field is the empty list.
struct Foo { int[] repeatedInteger; }; - With default value —
= -
A mandatory field that is allowed to be absent in serialized data; the decoder then sets it to the declared default. A default value may only be attached to an otherwise mandatory field: combining
=with?or[]is an error (default values for non required fields makes no sense).struct Foo { int val = 42; };
3. Default values
The default value must be compatible with the field type:
-
integer fields: an integer constant expression (literals with optional unit suffixes, arithmetic operators, enum value constants) or a character literal
c"x"; any other kind is an error (invalid default value on integer field). The value must fit the field type’s range; a negative value on an unsigned field is an error (invalid default value on unsigned integer field); -
double fields: a floating-point or integer literal. A string default on a double field is an error (
string default value on double field); -
bool fields:
trueorfalse(an integer expression is also accepted; any other kind isinvalid default value on bool field); -
string and bytes fields: a string literal; any other kind is an error (
invalid default value on string field); -
enum fields: the constant name of one of the enum’s values, written with the enum’s prefix: the enum type name converted to
UPPER_SNAKE_CASE(or the@prefixoverride), an underscore, and the value name:enum MyEnum { VAL_1 = 1, VAL_2 = 2, }; struct Foo { MyEnum foo = MY_ENUM_VAL_1; }; -
struct, union, class fields: cannot have default values;
-
void fields: cannot have default values (
default values are forbidden for void types).
The default value must also satisfy the field’s
constraint attributes (violation of @min
constraint, violation of @max constraint, violation of @nonZero
constraint, violation of @maxLength constraint, …), and for a
@strict enum it must be one of the declared values (invalid default
value on strict enum field).
4. Tags
Every field of a struct, union or class carries a tag: a small integer that identifies the field in the binary representation (wire format) and that, together with the rules of backward compatibility, constitutes the stable identity of the field.
-
Valid tags range from 1 to 32767 (0x7FFF) inclusive. Out-of-range explicit tags are errors (
tag is too small (must be >= 1),tag is too large (must be < 0x8000)). -
Tags are assigned implicitly by a counter starting at 1: the first field gets tag 1, the next tag 2, and so on.
-
An explicit tag may be given as an integer prefix
N:; it sets the field’s tag to N and resets the counter so that the next untagged field gets N+1.
struct Foo {
1: int a;
2: int b;
3: int c;
int d; /*< implicitly tag 4 */
5: int e;
1024: int f; /*< legal, but small tags encode smaller */
};
Tags below 30 occupy a single byte in the binary encoding; prefer small tags.
5. References
A reference field is declared with & after the type:
struct Foo {
MyStruct& referencedStruct; /* mandatory, held by reference */
};
A reference is semantically a mandatory field; the & only changes the
in-memory representation (the value is pointed to rather than inlined) and
the dependency rules. Restrictions:
-
the type must be a struct or union — scalars, enums and classes cannot be referenced (
references can only be applied to structures or unions); classes need no&, they are always held by reference; -
only mandatory fields can be references:
&combines with neither?nor[]nor a default value (references can only be applied to required fields,only mandatory fields can be references). The formsType&?andType?&are syntax errors; -
static class fields cannot be references (
referenced static members are forbidden).
A reference field is a strong dependency for the circular-dependency check, except as a member of a union with at least two members — the construct that makes recursive types possible (Dependencies and cycles).
6. Typedefs
A typedef creates a new name for a type, with optional cardinality modifiers and attributes:
[<attributes>] typedef <type>[? | [] | &] <UpperName> ;
typedef int MyInt;
@min(3)
typedef int MyIntMin3;
typedef string[] MyStringArray;
@allow(a)
typedef MyUnion MyUnionA;
typedef otherPkg.OtherStruct ExternalStruct;
Rules:
-
anything can be typedef’d: primitive types, enums, structs, unions, classes, and other typedefs (chains are allowed and attributes accumulate along the chain);
-
the typedef name follows type-name rules (uppercase first letter, no underscore);
-
using a typedef applies its kind, target type and attributes to the use site. Constraint attributes from the typedef chain and from the use site accumulate;
-
cardinality merge: if both the typedef and the use site specify a non-mandatory cardinality and they differ, the use is an error (
cannot declare repeated optional fields) — e.g. an optional typedef used as a repeated field, or an array typedef used with?. Otherwise the typedef’s cardinality applies; -
typedef cycles are errors (
recursive typedef for type 'X' in pkg 'Y'); -
attributes attached to a typedef must be applicable to the resolved type and cardinality (e.g.
@minOccurson a non-repeated typedef isattribute minOccurs does not apply to required typedefs;@patternon an integer typedef isattribute pattern does not apply to integer); -
a class may inherit from a typedef that resolves to a class.
Typedefs also serve to move types across packages without breaking the name-based (JSON/YAML/XML) representations: after moving a type to another package, leave behind a typedef with the original name pointing at the new location (see Backward compatibility).
7. The void type
void denotes presence without data. Its allowed contexts:
| Context | Legal | Notes |
|---|---|---|
union member |
yes |
distinguishes a case carrying no data |
mandatory struct field |
yes |
carries no information; idiomatic replacement for a removed field (keeps its tag reserved) |
optional struct field |
yes |
a pure presence flag |
|
yes |
the one case where |
repeated |
no |
|
|
no |
|
RPC argument fields |
yes |
including optional
|
|
yes |
declares the payload empty — see Interfaces |
anywhere in SNMP interfaces |
no |
|