pub unsafe trait KnownLayout {
type PointerMetadata: PointerMetadata;
}
Expand description
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.:
#[derive(KnownLayout)]
struct MyStruct {
...
}
#[derive(KnownLayout)]
enum MyEnum {
...
}
#[derive(KnownLayout)]
union MyUnion {
...
}
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:
- Like slices, slice DSTs can have different lengths at runtime
- Like slices, slice DSTs cannot be passed by-value, but only by reference
or via other indirection such as
Box
- Like slices, a reference (or
Box
, or other pointer type) to a slice DST encodes the number of elements in the trailing slice field
§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 presenence 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:
- If
T: Sized
, then we mean thatx == size_of::<T>()
- If
T
is a slice DST, then we mean that there exists alen
such that the instance ofT
withlen
trailing slice elements has sizex
When we say “largest possible size of T
that fits in a byte slice”, we
mean one of two things:
- If
T: Sized
, then we meansize_of::<T>()
if the byte slice is at leastsize_of::<T>()
bytes long - If
T
is a slice DST, then we mean to consider all values,len
, such that the instance ofT
withlen
trailing slice elements fits in the byte slice, and to choose the largest suchlen
, if any
§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.
Required Associated Types§
Sourcetype PointerMetadata: PointerMetadata
type PointerMetadata: PointerMetadata
The type of metadata stored in a pointer to Self
.
This is ()
for sized types and usize
for slice DSTs.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.