Derive Macro musli_zerocopy::ZeroCopy

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

Derive macro to implement ZeroCopy.

Implementing this trait ensures that the type can safely be coerced to and from initialized bytes.


§Using with structs

The following are the requirements for deriving structs:

  • The struct must either be #[repr(C)] or [repr(transparent)].
  • All fields in the struct must either implement ZeroCopy or be ZeroSized and marked as #[zero_copy(ignore)].

If the struct is zero-sized, it will implement ZeroSized along with the ZeroCopy trait.

use musli_zerocopy::{OwnedBuf, ZeroCopy};

#[derive(Debug, PartialEq, ZeroCopy)]
#[repr(C, align(128))]
struct Custom { field: u32 }

let mut buf = OwnedBuf::new();
let ptr = buf.store(&Custom { field: 10 });
buf.align_in_place();
assert_eq!(buf.load(ptr)?, &Custom { field: 10 });

§Using with enums

The following are the requirements for deriving for enums:

The enum must be marked with a valid, fixed representation. Such as #[repr(u8)], or #[repr(usize)].

use musli_zerocopy::ZeroCopy;

#[derive(ZeroCopy)]
#[repr(u8)]
enum Flags {
    First ,
    Second(u32),
    Third {
        first: u32,
        second: u64,
    },
}

If a custom discriminant is used, only constant expressions are supported.

For example:

  • A literal like 42,
  • A simple constant expression like u32::MIN + 10.
  • A more complex constant expression like my_constant_fn(u32::MIN >> 2).
const fn my_constant_fn(base: u8) -> u8 {
    base + 3
}

#[derive(ZeroCopy)]
#[repr(u8)]
enum Flags {
    First = 1,
    Second(u32), // will automatically assigned 2
    Third {
        first: u32,
        second: u64,
    } = u8::MAX,
    Fourth = my_constant_fn(u8::MIN >> 2),
}

Complete example:

use musli_zerocopy::{OwnedBuf, ZeroCopy};

#[derive(Debug, PartialEq, ZeroCopy)]
#[repr(u8)]
enum Flags {
    First,
    Second(u32),
    Third { first: u32, second: u64 },
}

let mut buf = OwnedBuf::with_alignment::<Flags>();

buf.clear();
let ptr = buf.store(&Flags::First);
assert_eq!(buf.load(ptr)?, &Flags::First);

buf.clear();
let ptr = buf.store(&Flags::Second(42));
assert_eq!(buf.load(ptr)?, &Flags::Second(42));

buf.clear();
let ptr = buf.store(&Flags::Third { first: 42, second: 84 });
assert_eq!(buf.load(ptr)?, &Flags::Third { first: 42, second: 84 });

§Padding

The constant ZeroCopy::Padded determines whether the derives struct uses padding or not. This derive currently uses a fairly conservative algorithm:

The constant ZeroCopy::Padded will be set to true if:

  • The size of the type is 0, and the alignment is larger than 1. This indicates a zero-sized type with an explicit #[repr(align(N))] that is not set to 1.
  • The sum of the size of all the fields is not the same as the size of the type.
  • Any of the fields has its ZeroCopy::Padded set to true.
  • For enums, we test every variant with the same rules, except each variant is treated as a struct where the discriminant (u32 in #[repr(u32)]) is treated like [a leading hidden field].
use musli_zerocopy::ZeroCopy;

#[derive(ZeroCopy)]
#[repr(C)]
struct Zst;
const _: () = assert!(!Zst::PADDED);

#[derive(ZeroCopy)]
#[repr(C, align(1))]
struct ZstAlign1;
const _: () = assert!(!ZstAlign1::PADDED);

#[derive(ZeroCopy)]
#[repr(C, align(128))]
struct ZstPadded;
const _: () = assert!(!ZstPadded::PADDED);

#[derive(ZeroCopy)]
#[repr(u8)]
enum ZstEnum { EmptyField }
const _: () = assert!(!ZstEnum::PADDED);

#[derive(ZeroCopy)]
#[repr(u8)]
enum SameEnum { Variant1(u8), Variant2(u8) }
const _: () = assert!(!SameEnum::PADDED);

#[derive(ZeroCopy)]
#[repr(u16)]
enum PaddedU16 { Variant1(u8), Variant2(u8) }
const _: () = assert!(PaddedU16::PADDED);

#[derive(ZeroCopy)]
#[repr(u16)]
enum NotPaddedU16 { Variant1(u8, u8), Variant2([u8; 2]), Variant3(u16) }
const _: () = assert!(!NotPaddedU16::PADDED);

#[derive(ZeroCopy)]
#[repr(C, packed)]
struct Packed { inner: u8, inner2: u32 }
const _: () = assert!(!Packed::PADDED);

#[derive(ZeroCopy)]
#[repr(C, packed(2))]
struct Packed1 { inner: u8, inner2: u32 }
const _: () = assert!(Packed1::PADDED);

#[derive(ZeroCopy)]
#[repr(C)]
struct Inner { inner: u8, inner2: u32 }
const _: () = assert!(Inner::PADDED);

#[derive(ZeroCopy)]
#[repr(C)]
struct Outer { first: u8, inner: Inner }
const _: () = assert!(Outer::PADDED);

§Supported attributes


§Type attributes

The following repr attributes are supported:

  • #[repr(C)] - Ensures that the type has the mandatory represention.
  • #[repr(transparent)] - If there is a single field inside of the marked struct which implements ZeroCopy.
  • #[repr(align(N))] - Allows for control over the type’s alignment.
  • #[repr(packed)] or #[repr(packed(N))] - Allows for control over the struct alignment. Namely to lower it. This is not supported for enums.

The following zero_copy(..) attribute are supported:


§#[zero_copy(bounds = {<bound>,*})]

Allows for adding additional bounds to implement ZeroCopy for generic types:

use musli_zerocopy::ZeroCopy;

#[derive(ZeroCopy)]
#[repr(C)]
#[zero_copy(bounds = {A: ZeroCopy, B: ZeroCopy})]
struct Pair<A, B> {
    left: A,
    right: B,
}

§#[zero_copy(crate = <path>)]

Allows for specifying a custom path to the `musli_zerocopy`` crate (default).

use zerocopy::ZeroCopy;

#[derive(ZeroCopy)]
#[repr(C)]
#[zero_copy(crate = zerocopy)]
struct Custom { field: u32 }