pub trait Packable: Sized + 'static {
type UnpackError: Debug + From<Infallible>;
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error>;
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>>;
}
Expand description
A type that can be packed and unpacked.
Almost all basic sized types implement this trait. This trait can be derived using the
Packable
macro. The following example shows how to implement this trait manually.
Example
We will implement Packable
for a type that encapsulates optional integer values (like Option<i32>
).
We will use an integer prefix as a tag to determine which variant of the enum is being packed.
use core::convert::Infallible;
use packable::{
error::{UnknownTagError, UnpackError, UnpackErrorExt},
packer::Packer,
unpacker::Unpacker,
Packable,
};
pub enum Maybe {
Nothing,
Just(i32),
}
impl Packable for Maybe {
type UnpackError = UnknownTagError<u8>;
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
match self {
// Pack a `0` byte and nothing else.
Self::Nothing => 0u8.pack(packer),
// Pack a `1` byte followed by the internal value.
Self::Just(value) => {
1u8.pack(packer)?;
value.pack(packer)
}
}
}
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U,
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
match u8::unpack::<_, VERIFY>(unpacker).coerce()? {
0u8 => Ok(Self::Nothing),
1u8 => Ok(Self::Just(i32::unpack::<_, VERIFY>(unpacker).coerce()?)),
tag => Err(UnpackError::Packable(UnknownTagError(tag))),
}
}
}
To understand the behavior of infallible
and coerce
check the UnpackError
and
UnpackErrorExt
documentation.
We can also derive Packable
for the Maybe
type.
use packable::Packable;
#[derive(Packable)]
#[packable(tag_type = u8)]
pub enum Maybe {
#[packable(tag = 0)]
Nothing,
#[packable(tag = 1)]
Just(i32),
}
The code produced by this macro is equivalent to the one shown before.
#[derive(Packable)]
attributes
The derive implementation can be tweaked using #[packable(...)]
attributes.
Tags for enums
A very common pattern when implementing Packable
for enums consists in introducing a prefix
value to differentiate each variant of the enumeration when unpacking, this prefix value is
known as a tag
. The type of the tag
is specified with the #[packable(tag_type = ...)]
attribute and it can only be one of [u8]
, [u16]
, [u32]
or [u64]
. The tag
value used
for each variant is specified with the #[packable(tag = ...)]
attribute and can only contain
integer literal without any type prefixes (e.g. 42
is valid but 42u8
is not).
In the example above, the tag
type is [u8]
, the Nothing
variant has a tag
value of 0
and the Just
variant has a tag
value of 1
. This means that the packed version of
Maybe::Nothing
is [0]
and the packed version of Maybe::Just(7)
is [1, 0, 0, 0, 7]
.
The tag_type
and tag
attributes are mandatory for enums unless the enum has a
#[repr(...)]
attribute identifier, in which case the repr
type will be used as the
tag_type
and each variant discriminant will be used as the tag
. The tag_type
and tag
attributes take precedence over the repr
attribute.
The UnpackError
associated type
The derive macro provides the optional attribute and #[packable(unpack_error = ...)]
to
specify the UnpackError
associated type. The macro also provides sensible defaults for cases
when the attribute is not used.
For structs, the default UnpackError
type is the
UnpackError
of any of the fields type or
Infallible
in case the struct has no fields.
For enums, the default UnpackError
type is
UnknownTagError<T>
where T
is the type specified according
to the tag_type
or repr
attributes.
Following the example above, Maybe::UnpackError
is UnknownTagError<u8>
because no
unpack_error
attribute was specified.
Error conversion
The unpack_error
attribute can also receive an optional additional argument using the with
identifier: #[packable(unpack_error = ..., with = ...)]
. This with
argument must be a Rust
expression and it is used to map the UnpackError
produced while unpacking each one of the
fields of the type.
Sometimes it is required to map the UnpackError
for each field individually. The
#[packable(unpack_error_with = ...)]
attribute can be applied to each field for this purpose.
This attribute takes precedence over the with
expression specified in the unpack_error
attribute.
The error produced when an invalid tag
is found while unpacking an enum
can also be
specified using the with_error
optional argument for the tag_type
attribute:
#[packable(tag_type = ..., with_error = ...)]
. This argument must be a valid Rust expression.
Additional semantic verifications
From time to time it is required to do additional semantic verifications over one of more
fields of a struct
or an enum
’s variant. This can be done using the
#[packable(verify_with = ...)]
attribute which must receive a valid Rust path refering to a
function with the signature
fn<const VERIFY: bool>(field: &F) -> Result<(), P::UnpackError>
where F
is the type of the field being verified, P
is the type of the struct
or enum
and VERIFY
is the same constant parameter used inside Packable::unpack
. This verification
function will be run immediately after unpacking the field.
Required Associated Types
type UnpackError: Debug + From<Infallible>
type UnpackError: Debug + From<Infallible>
The error type that can be returned if some semantic error occurs while unpacking.
It is recommended to use Infallible
if this kind of error is impossible or
UnknownTagError
when implementing this trait for an enum.
Required Methods
Packs this value into the given Packer
.
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>>
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>>
Unpacks this value from the given Unpacker
. The VERIFY
generic parameter can be used to skip additional
syntactic checks.
Implementations on Foreign Types
sourceimpl<T: Packable> Packable for Option<T>
impl<T: Packable> Packable for Option<T>
Options are packed and unpacked using 0u8
as the prefix for None
and 1u8
as the prefix for Some
.
type UnpackError = UnpackOptionError<<T as Packable>::UnpackError>
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error>
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>>
sourceimpl<T: Packable, const N: usize> Packable for [T; N]
impl<T: Packable, const N: usize> Packable for [T; N]
type UnpackError = <T as Packable>::UnpackError
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error>
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>>
sourceimpl Packable for bool
impl Packable for bool
sourcefn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error>
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error>
Booleans are packed as u8
integers following Rust’s data layout.
sourcefn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>>
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>>
Booleans are unpacked if the byte used to represent them is non-zero.