pub unsafe trait Initializer<T: VarLen> {
    fn calculate_layout_cautious(&self) -> Option<T::Layout>;
unsafe fn initialize(self, dst: NonNull<T>, layout: T::Layout); }
Expand description

A type that knows how to construct a variable-length object.

Construction of a variable-length object proceeds as follows:

  1. The Initializer calculates how much memory is required for the object, returns that information to the caller.
  2. The caller, such as VBox or Seq, reserves that much memory for the object.
  3. The Initializer populates the reserved memory.

An Initializer is responsible for steps (1) and (3) of this protocol. Step (1) is handled by calculate_layout_cautious, and step (3) is handled by initialize.

Directly defining or using an Initializer involves writing unsafe code. You can typically avoid this unsafe code by using the standard container types such as VBox aod Seq and the standard initializers that the tuple/struct/array/string types provide.

Examples

Full lifecycle of initialization and destruction:

use varlen::prelude::*;
use varlen::Layout as _;
use std::alloc::Layout;
use std::pin::Pin;
use std::ptr::NonNull;

// Make an initializer. This is a cheap pointer-only operation.
let init = Str::copy("hello world");

// Step 1: calculate memory requirement.
let layout = init.calculate_layout_cautious().unwrap();
let alloc_layout = Layout::from_size_align(layout.size(), Str::<usize>::ALIGN).unwrap();
let mut s: Pin<&mut Str> = unsafe {
    // Step 2: allocate memory.
    let mem = std::alloc::alloc(alloc_layout);
    let mut mem = NonNull::new(mem as *mut Str).unwrap();
    // Step 3: populate the memory.
    init.initialize(mem, layout);
    // Now use the initialized object
    Pin::new_unchecked(mem.as_mut())
};
// Read its value
assert_eq!(&s[..], "hello world");
// Eventually delete it
unsafe {
    let layout = s.calculate_layout();
    let ptr = s.as_mut().get_unchecked_mut() as *mut _ as *mut u8;
    s.vdrop(layout);
    std::alloc::dealloc(ptr, alloc_layout);
}

Safety

An implementor of this trait is required to ensure:

  • When self.initialize(dst, layout) is called with layout=self.calculate_layout_cautious(), the initialize() function doesn’t write past offset layout.size() of dst.
  • When initialize(dst, layout) is called with layout=self.calculate_layout_cautious(), then after the initialize()function returns the objectTis "initialized", i.e. it is safe to use as a&Tor as aPin<&mut T>`.
  • The layout returned by calculate_layout_cautious() matches the layout returned by VarLen::calculate_layout() on the initialized object.

Required methods

Calculates the layout of the object, returning None if any of the calculated sizes or offsets would overflow usize.

Examples
use varlen::prelude::*;
use varlen::Layout as _;

// Succeeds on most sizes:
let init = SizedInit(4usize, FillWithDefault);
assert_eq!(
    Initializer::<Array<u32>>::calculate_layout_cautious(&init)
    .unwrap().size(),
    std::mem::size_of::<usize>() + 4 * std::mem::size_of::<u32>());
// Fails on `usize` overflow:
let init = SizedInit(usize::MAX, FillWithDefault);
assert!(
   Initializer::<Array<u32>>::calculate_layout_cautious(&init)
   .is_none());

Populates the destination pointer.

See the trait documentation for details on the initialization protocol.

Safety

Trait implementor requirements are documented in the trait documentation.

Additionally, the function caller must guarantee:

  • You must call initialize with the layout returned by calculate_layout_cautious() on the same initializer object.
  • dst must be writable, with size as specified by self.calculate_layout().size().

Implementors

VBox<T> is an initializer for T.

Examples

Pushing a VBox<T> onto a crate::seq::Seq<T>:

use varlen::prelude::*;

let mut seq: Seq<Str> = Seq::new();
let b = VBox::new(Str::copy("hello"));
seq.push(b);