How to convert between bytes and a nested `struct`
The example below encasts[^encast] a byte representation of nested
struct `Container` in little-endian as a value of the type in
native-endian, then decasts[^decast] the value as a byte
representation of struct `Container` in little-endian.
representation of a type as a value of the type.
as a byte representation of the type.
# Outline
- Step 1: Struct `Container`, struct `FieldS` and union `FieldU` are
defined.
- They implement trait [`Cast`] by applying both attribute
`#[`[`derive(Cast)`]`]` and attribute `#[`[`repr(C)`]`]` to them.
- Struct `Container` and struct `FieldS` implement trait [`Flip`]
by applying attribute `#[`[`derive(Flip)`]`]` to them.
- Union `FieldU` implements both trait [`Flip`] whose methods do
nothing and trait [`NopFlip`] by applying attribute
`#[`[`derive(NopFlip)`]`]` to it.
- Step 2: Method [`EncastMem::encastf`] encasts a byte
representation in little-endian ([`LE`]) as a value of struct
`Container` in native-endian.
- Step 3: Method [`DecastMem::decastf`] decasts a value of
struct `Container` in native-endian as a byte representation in
little-endian ([`LE`]).
# Source Code
```rust
use castflip::{Cast, DecastMem, EncastMem, Flip, LE, NopFlip};
//
// Step 1: Define nested struct `Container` and test data.
//
#[repr(C)] // to make it possible to apply #[derive(Cast)]
#[derive(Cast, Flip)] // to implement trait Cast and trait Flip
struct Container { // 16 bytes (in total)
s: FieldS, // 8 bytes
u: FieldU, // 4 bytes
f: f32, // 4 bytes
}
#[repr(C)] // to make it possible to apply #[derive(Cast)]
#[derive(Cast, Flip)] // to implement trait Cast and trait Flip
struct FieldS { // 8 bytes (in total)
s1: [u8; 2], // 2 bytes
s2: u16, // 2 bytes
s3: u32, // 4 bytes
}
#[repr(C)] // to make it possible to apply #[derive(Cast)]
#[derive(Cast, NopFlip)] // to implement trait Cast, trait Flip & trait NopFlip
union FieldU { // 4 bytes (the largest)
u1: [u8; 4], // 4 bytes
u2: [u16; 2], // 4 bytes
u3: u32, // 4 bytes
}
// Test data: A sample byte representation of struct `Container`
// (16 bytes in little-endian)
const BYTES1: [u8; 16] = [
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x00, 0x00, 0x48, 0x41,
];
fn main() {
//
// Step 2: Method encastf (1) encasts a byte representation of
// struct `Container` at the head of const `BYTES1` as a value of the
// type, (2) flips the endiannesses of field `s` of struct `FieldS`
// and field `f` of f32 from the little-endianness (`LE`) to the
// native-endianness (Note: field `u` of union `FieldU` is not flipped),
// and (3) returns the resulting value in Ok(Container) which is saved
// to variable `con2`.
//
// In the following method call, generic argument `Container`
// can be omitted because it can be infered by the Rust compiler.
//
let con2: Container = BYTES1.encastf::<Container>(LE).unwrap();
// Check if the values of all fields in variable `con2.s` (whose type
// is struct `FieldS` in struct `Container`) are as expected.
assert_eq!(con2.s.s1, [0x10, 0x11]); // s1: [u8; 2],
assert_eq!(con2.s.s2, 0x1312); // s2: u16,
assert_eq!(con2.s.s3, 0x17161514); // s3: u32,
// Check if the values of all fields in variable `con2.u` (whose type
// is union `FieldU` in struct `Container`) are evaluated as expected.
// Note that their endiannesses are retained.
unsafe {
assert_eq!(con2.u.u1, [0x18, 0x19, 0x1a, 0x1b]); // u1: [u8; 4],
if cfg!(target_endian = "little") {
assert_eq!(con2.u.u2, [0x1918, 0x1b1a]); // u2: [u16; 2],
assert_eq!(con2.u.u3, 0x1b1a1918); // u3: u32,
} else if cfg!(target_endian = "big") {
assert_eq!(con2.u.u2, [0x1819, 0x1a1b]); // u2: [16; 2],
assert_eq!(con2.u.u3, 0x18191a1b); // u3: u32,
# } else {
# panic!();
}
}
// Check if the value of variable `con2.f` (whose type is f32 in
// struct `Container`) is as expected.
assert_eq!(con2.f, 12.5_f32); // f: f32,
//
// Step 3: Method decastf (1) decasts the value of struct
// `Container` saved in variable `con2` as a byte representation of the
// type, (2) flips the endiannesses of field `s` of struct `FieldS` and
// field `f` of f32 from the native-endianness to the little-endianness
// (`LE`) (Note: field `u` of union `FieldU` is not flipped), (3) saves
// the resulting bytes to variable `bytes3` and (4) returns the number
// of the saved bytes in Ok(usize) which is saved in variable `size3`.
//
// In the following method call, generic argument `Container`
// can be omitted because it can be infered by the Rust compiler.
//
let mut bytes3 = [0_u8; 16];
let size3 = bytes3.decastf::<Container>(&con2, LE).unwrap();
// Check if the value in variable `size3` is as expected.
assert_eq!(size3, 16); // The size of struct `Container` is 16.
// Check if the content of variable `bytes3` is as expected.
// The endianness of field `u` of union `FieldU` is retained.
assert_eq!(bytes3, BYTES1);
}
```
[`derive(Cast)`]: ../../derive.Cast.html
[`derive(Flip)`]: ../../derive.Flip.html
[`derive(NopFlip)`]: ../../derive.NopFlip.html
[`repr(C)`]: https://doc.rust-lang.org/reference/type-layout.html#the-c-representation