Standard Rust [DST] references comprise not only a pointer to the underlying object, but also some associated metadata by which the DST can be resolved at runtime. Both the pointer and the metadata reside together in a so-called "fat" or "wide" reference, which is typically what one wants: the referent can thus be reached with only direct memory accesses, and each reference can refer to a different DST (for example, slices or traits) of a single underlying object.
However, in order to store multiple copies of the same DST reference, one
typically suffers either metadata duplication across each such reference or
else some costly indirection; furthermore, in some situations Rust's
metadata may be known to be excessive: some other (smaller) type may suffice
instead (for example, with particular slices, we may know that their lengths
will always fit in a u8
and hence using a usize
for their metadata would
be unnecessary; or, with particular trait objects, we may know that the
underlying type will always be from some enum
and hence using a vtable
pointer for their metadata would be unnecessary). Addressing these concerns
can save valuable memory, especially when there are a great many such
references in simultaneous use.
A [Thinnable
] stores the metadata together with the referent rather than
the reference, and thereby enables one to obtain "thin" DST references to
the (now metadata-decorated) object: namely, [ThinRef
] and [ThinMut
] for
shared and exclusive references respectively. But this comes at the cost of
an additional indirect memory access in order to fetch the metadata, rather
than it being directly available on the stack; hence, as is so often the
case, we are trading off between time and space. Furthermore, for any given
[Thinnable
], its "thin" references can only refer to the one DST with
which that [Thinnable
] was instantiated (although regular "fat" references
can still be obtained to any of its DSTs, if required).
One can specify a non-standard metadata type, e.g. to use a smaller integer
such as u8
in place of the default usize
when a slice fits within its
bounds: a [MetadataCreationFailure
] will arise if the metadata cannot be
converted into the proposed type. Using such a non-standard metadata type
may save some bytes of storage, but obviously adds additional conversion
overhead on every dereference.
The [ThinnableSlice<T>
] type alias is provided for more convenient use
with [T]
slices; and the [ThinnableSliceU8<T>
], [ThinnableSliceU16<T>
]
and [ThinnableSliceU32<T>
] aliases provide the same convenient use with
[T]
slices but encoding the length metadata in a u8
, u16
or u32
respectively.
This crate presently depends on Rust's unstable [ptr_metadata
] and
[unsize
] features and, accordingly, requires a nightly toolchain.
```rust use core::{alloc::Layout, convert::TryFrom, fmt, mem}; use thinnable::*;
const THINSIZE: usize = mem::sizeof::<&()>();
// Creating a thinnable slice for an array is straightforward. let thinnable_slice = ThinnableSlice::new([1, 2, 3]);
// Given a thinnable, we can obtain a shared reference... let r = thinnableslice.asthinref(); // ThinRef<[u16]> // ...which is "thin".... asserteq!(mem::sizeofval(&r), THINSIZE); // ...but which otherwise behaves just like a regular "fat" DST reference asserteq!(&r[..2], &[1, 2]);
// For types M
where the metadata type implements TryInto<M>
, we can use
// Thinnable::try_new
to try creating a thinnable using M
as its stored
// metadata type (dereferencing requires that M: TryInto<R>
where R
is
// the original metadata type).
//
// For slices, there's a slightly more ergonomic interface:
let sizedefault = mem::sizeofval(&thinnableslice);
let mut thinnableslice = ThinnableSliceU8::tryslice([1, 2, 3]).unwrap();
let sizeu8 = mem::sizeofval(&thinnableslice);
assert!(sizeu8 < sizedefault);
// We can also obtain a mutable reference... let mut m = thinnableslice.asthinmut(); // ThinMut<[u16], u8> // ...which is also "thin".... asserteq!(mem::sizeofval(&m), THINSIZE); // ...but which otherwise behaves just like a regular "fat" DST reference. m[1] = 5; asserteq!(&m[1..], &[5, 3]);
// We can also have thinnable trait objects:
let thinnabletraitobject = Thinnable::<_, dyn fmt::Display>::new(123);
let o = thinnabletraitobject.asthinref(); // ThinRef