Derive Macro concordium_std::Serial

source ·
#[derive(Serial)]
{
    // Attributes available to this derive:
    #[concordium]
}
Expand description

Derive the Serial trait for the type.

If the type is a struct all fields must implement the Serial trait. If the type is an enum then all fields of each of the variants must implement the Serial trait.

Fields of structs are serialized in the order they appear in the code.

§Enums

Enums can have no more than 65536 variants. They are serialized by using a tag to indicate the variant, and by default they are enumerated in the order they are written in the source code. If the number of variants is less than or to equal 256 then a single byte is used to encode it. Otherwise two bytes are used for the tag, encoded in little endian.

§Specifying the tag byte size using #[concordium(repr(..))]

Optionally, an enum type can be annotated with a #[concordium(repr(x))] attribute, where x is either u8 or u16. This specifies the number of bytes to use when serializing the tag in little endian.

A type annotated with #[concordium(repr(u8))] can only have up to 256 variants and #[concordium(repr(u16))] can have up to 65536 variants.

§Example

Example of an enum which uses two bytes for encoding the tag. Here the variant A is tagged using 0u16 and B is tagged using 1u16.

#[derive(Serial)]
#[concordium(repr(u16))]
enum MyEnum {
    A,
    B
}

§Specifying the tag value for a variant using #[concordium(tag = ..)]

For each enum variant the tag can be explicitly set using #[concordium(tag = n)] where n is the integer literal to use for the tag. When using the ‘tag’ attribute it is required to have the #[concordium(repr(..))] set as well. The tag must have a value representable by the type set by #[concordium(repr(..))].

Note that SchemaType currently only supports using a single byte #([concordium(repr(u8))]) when using #[concordium(tag = ..)].

§Nesting enums with a flat serialization using #[concordium(forward = ...)]

Often it is desired to have a single type representing a parameter or the events. A general pattern for enums is to nest them, however deriving serialization for a nested enum introduces an additional tag for the variant of the top-level enum. The solution is to use the attribute #[concordium(forward = ...)] on the variant with a nested enum. This attribute takes a tag or a list of tags which changes the serialization to skip the variant tag and deserialization to match the variant with these tags and forward the deserialization to the nested enum.

#[derive(Serial, Deserial)]
#[concordium(repr(u8))]
enum Event {
    SomeEvent(MyEvent),
    #[concordium(forward = [42, 43, 44, 45])]
    OtherEvent(NestedEvent),
}

For convenience the attribute also supports the values cis2_events, cis3_events and cis4_events which are unfolded to the list of tags used for events in CIS-2, CIS-3 and CIS-4 respectively.

#[derive(Serial, Deserial)]
#[concordium(repr(u8))]
enum Event {
    SomeEvent(MyEvent),
    #[concordium(forward = cis2_events)]
    Cis2(Cis2Event),
}

Setting #[concordium(forward = n)] on a variant will produce an error if:

  • The type does not have a #[concordium(repr(u*))] attribute.
  • If any of the forwarded tags n cannot be represented by the #[concordium(repr(u*))].
  • Any of the forwarded tags n overlap with a tag of another variant.
  • n contains a predefined set and the value of #[concordium(repr(u*))] is incompatible.
  • If the variant does not have exactly one field.

Note that the derive macro does not check forwarded tags matches the tags of the inner type.

§Example

Example of enum specifying the tag of the variant A to the value 42u8. The variant B is tagged using 1u8.

#[derive(Serial)]
#[concordium(repr(u8))]
enum MyEnum {
    #[concordium(tag = 42)]
    A,
    B
}

§Generic type bounds

By default a trait bound is added on each generic type for implementing Serial. However, if this is not desirable, the default bound can be replaced by using the bound attribute on the type and providing the replacement.

Bounds present in the type declaration will still be present in the implementation, even when a bound is provided:

§Example

#[derive(Serial)]
#[concordium(bound(serial = "A: SomeOtherTrait"))]
struct Foo<A: SomeTrait> {
    bar: A,
}

// Derived implementation:
impl <A: SomeTrait> Serial for Foo<A> where A: SomeOtherTrait { .. }

§Collections

Collections (Vec, BTreeMap, BTreeSet) and strings (String, str) are by default serialized by prepending the number of elements as 4 bytes little-endian. If this is too much or too little, fields of the above types can be annotated with size_length.

The value of this field is the number of bytes that will be used for encoding the number of elements. Supported values are 1, 2, 4, 8.

For BTreeMap and BTreeSet the serialize method will serialize values in increasing order of keys.

§Example

#[derive(Serial)]
struct Foo {
    #[concordium(size_length = 1)]
    bar: BTreeSet<u8>,
}