Trait KnownLayout

unsafe trait KnownLayout

Indicates that zerocopy can reason about certain aspects of a type's layout.

This trait is required by many of zerocopy's APIs. It supports sized types, slices, and slice DSTs.

Implementation

Do not implement this trait yourself! Instead, use #[derive(KnownLayout)]; e.g.:

# use zerocopy_derive::KnownLayout;
#[derive(KnownLayout)]
struct MyStruct {
# /*
    ...
# */
}

#[derive(KnownLayout)]
enum MyEnum {
# /*
    ...
# */
}

#[derive(KnownLayout)]
union MyUnion {
#   variant: u8,
# /*
    ...
# */
}

This derive performs a sophisticated analysis to deduce the layout characteristics of types. You must implement this trait via the derive.

Dynamically-sized types

KnownLayout supports slice-based dynamically sized types ("slice DSTs").

A slice DST is a type whose trailing field is either a slice or another slice DST, rather than a type with fixed size. For example:

#[repr(C)]
struct PacketHeader {
# /*
    ...
# */
}

#[repr(C)]
struct Packet {
    header: PacketHeader,
    body: [u8],
}

It can be useful to think of slice DSTs as a generalization of slices - in other words, a normal slice is just the special case of a slice DST with zero leading fields. In particular:

Slice DST layout

Just like other composite Rust types, the layout of a slice DST is not well-defined unless it is specified using an explicit #[repr(...)] attribute such as #[repr(C)]. Other representations are supported, but in this section, we'll use #[repr(C)] as our example.

A #[repr(C)] slice DST is laid out just like sized #[repr(C)] types, but the presence of a variable-length field introduces the possibility of dynamic padding. In particular, it may be necessary to add trailing padding after the trailing slice field in order to satisfy the outer type's alignment, and the amount of padding required may be a function of the length of the trailing slice field. This is just a natural consequence of the normal #[repr(C)] rules applied to slice DSTs, but it can result in surprising behavior. For example, consider the following type:

#[repr(C)]
struct Foo {
    a: u32,
    b: u8,
    z: [u16],
}

Assuming that u32 has alignment 4 (this is not true on all platforms), then Foo has alignment 4 as well. Here is the smallest possible value for Foo:

byte offset | 01234567
      field | aaaab---
                   ><

In this value, z has length 0. Abiding by #[repr(C)], the lowest offset that we can place z at is 5, but since z has alignment 2, we need to round up to offset 6. This means that there is one byte of padding between b and z, then 0 bytes of z itself (denoted >< in this diagram), and then two bytes of padding after z in order to satisfy the overall alignment of Foo. The size of this instance is 8 bytes.

What about if z has length 1?

byte offset | 01234567
      field | aaaab-zz

In this instance, z has length 1, and thus takes up 2 bytes. That means that we no longer need padding after z in order to satisfy Foo's alignment. We've now seen two different values of Foo with two different lengths of z, but they both have the same size - 8 bytes.

What about if z has length 2?

byte offset | 012345678901
      field | aaaab-zzzz--

Now z has length 2, and thus takes up 4 bytes. This brings our un-padded size to 10, and so we now need another 2 bytes of padding after z to satisfy Foo's alignment.

Again, all of this is just a logical consequence of the #[repr(C)] rules applied to slice DSTs, but it can be surprising that the amount of trailing padding becomes a function of the trailing slice field's length, and thus can only be computed at runtime.

What is a valid size?

There are two places in zerocopy's API that we refer to "a valid size" of a type. In normal casts or conversions, where the source is a byte slice, we need to know whether the source byte slice is a valid size of the destination type. In prefix or suffix casts, we need to know whether there exists a valid size of the destination type which fits in the source byte slice and, if so, what the largest such size is.

As outlined above, a slice DST's size is defined by the number of elements in its trailing slice field. However, there is not necessarily a 1-to-1 mapping between trailing slice field length and overall size. As we saw in the previous section with the type Foo, instances with both 0 and 1 elements in the trailing z field result in a Foo whose size is 8 bytes.

When we say "x is a valid size of T", we mean one of two things:

When we say "largest possible size of T that fits in a byte slice", we mean one of two things:

Safety

This trait does not convey any safety guarantees to code outside this crate.

You must not rely on the #[doc(hidden)] internals of KnownLayout. Future releases of zerocopy may make backwards-breaking changes to these items, including changes that only affect soundness, which may cause code which uses those items to silently become unsound.

Associated Types

type PointerMetadata: TraitBound { trait_: Path { path: "PointerMetadata", id: Id(3645), args: None }, generic_params: [], modifier: None }

The type of metadata stored in a pointer to Self.

This is () for sized types and usize for slice DSTs.

Provided Methods

fn size_for_metadata(meta: <Self as >::PointerMetadata) -> Option<usize>

Computes the size of an object of type Self with the given pointer metadata.

Safety

size_for_metadata promises to return None if and only if the resulting size would not fit in a usize. Note that the returned size could exceed the actual maximum valid size of an allocated object, isize::MAX.

Examples

use zerocopy::KnownLayout;

assert_eq!(u8::size_for_metadata(()), Some(1));
assert_eq!(u16::size_for_metadata(()), Some(2));
assert_eq!(<[u8]>::size_for_metadata(42), Some(42));
assert_eq!(<[u16]>::size_for_metadata(42), Some(84));

// This size exceeds the maximum valid object size (`isize::MAX`):
assert_eq!(<[u8]>::size_for_metadata(usize::MAX), Some(usize::MAX));

// This size, if computed, would exceed `usize::MAX`:
assert_eq!(<[u16]>::size_for_metadata(usize::MAX), None);

Implementors