Expand description

This library is used to facilitate safe pinned initialization (what is this?) for as many parties involved as possible.

Jump to the relevant section:

Using this library requires a nightly version of rust, until the following features are stablized:

  • generic_associated_types
  • const_ptr_offset_from
  • const_refs_to_cell Users of the proc maco attributes manual_init and pinned_init will need to also add these features to their crate, because the macros generate code using these features.

Initialization of a type supported by this library

Lets say you use a crate defining a self referential data type named PtrBuf with support for this library.

Because this section is only about how we initialize such a type I will not show any implementation details. Instead I will use standard rust types to show what these type are conceptually made of. If you are interested how such a type actually is created, feel free to head to that section.

What PtrBuf defines

use pinned_init::prelude::*;
#[manual_init]
pub struct PtrBuf<T> {
    idx: *const T,
    buf: [T; 64],
}

To create a PtrBuf the crate provides the following implementation:

impl<T> From<[T; 64]> for PtrBufUninit<T> {
    fn from(arr: [T; 64]) -> Self { todo!() }
}

The PtrBufUninit type is automatically generated by this library (invoked via the see manual_init proc macro) and represents a partially uninitialized, version of a PtrBuf.

To use a PtrBuf, a next function is defined:

impl<T> PtrBuf<T> {
    pub fn next(self: Pin<&mut Self>) -> Option<&T> { todo!() }
}

Creating a PtrBuf

To begin the creation process of a PtrBuf we first must create a PtrBufUninit:

let uninit: PtrBufUninit<i32> = PtrBufUninit::from([42; 64]);

Because this library facilitates pinned initialization, we now need to find some stable memory for our uninit, that we can allocate for at least the duration of the PtrBuf we intend to use.

In this Example we will use Box, but you could use any other mechanism to store your object, as long as it lives long enough and is supported by this library (see this section, if you want to know what this means).

Now we need to call Box::pin to create an already pinned pointer to our data, if your pointer type supports it you could also create it first and then pin it later, but in many cases this is not necessary.

let boxed: Pin<Box<PtrBufUninit<i32>>> = Box::pin(uninit);

Now all that is left to do is to call SafePinnedInit::init, this trait is implemented for Pin<P> when P is pointer with support for this library.

use pinned_init::prelude::*;
let init: Pin<Box<PtrBuf<i32>>> = boxed.init();

Full code:

#![feature(generic_associated_types, const_ptr_offset_from, const_refs_to_cell)]
use pinned_init::prelude::*;
let uninit: PtrBufUninit<i32> = PtrBufUninit::from([42; 64]);
let boxed: Pin<Box<PtrBufUninit<i32>>> = Box::pin(uninit);
let init: Pin<Box<PtrBuf<i32>>> = boxed.init();

Declaration of a type with field types supported by this library

This involves writing no unsafe code yourself and is done by adding pinned_init as an attribute to your struct and marking each field, that requires initialization with #[init]:

#![feature(generic_associated_types, const_ptr_offset_from, const_refs_to_cell)]
use pinned_init::prelude::*;
#[pinned_init]
pub struct WithPtrBuf<'a, T> {
    #[init]
    msgs: PtrBuf<&'a str>,
    #[init]
    values: PtrBuf<T>,
    info: String,
}

impl<'a, T> WithPtrBufUninit<'a, T> {
    pub fn new(info: String, msgs: [&'a str; 64], values: [T; 64]) -> Self {
        Self {
            msgs: msgs.into(),
            values: values.into(),
            info,
        }
    }
}

And that is it.

When you want to use a field, use the same API when using pin_project:

#[pinned_init]
pub struct WithPtrBuf<'a, T> {
    #[init]
    msgs: PtrBuf<&'a str>,
    #[init]
    values: PtrBuf<T>,
    info: String,
}
impl<'a, T> WithPtrBuf<'a, T> {
    pub fn print_msgs(self: Pin<&mut Self>) {
        let mut this = self.project();
        while let Some(msg) = this.msgs.as_mut().next() {
            println!("{msg}");
        }
    }
}

Declaration of a type with field types not supported by this library

When using a field with a type which is not supported (for example an uninitialized type or something that requires custom logic) you will not be able to use pinned_init. Instead you will use manual_init and additionally manually implement PinnedInit. The ussage of manual_init is similar to pinned_init, you mark fields that need initialization with #[init]. However you need to specify #[pin] manually, if you want that field to be structually pinned. When you want to initalize a field only after pinning and that fields type does not implement PinnedInit, you need to use StaticUninit<T, INIT> here the PtrBuf example:

#![feature(generic_associated_types, const_ptr_offset_from, const_refs_to_cell)]
use pinned_init::prelude::*;
#[manual_init]
pub struct PtrBuf<T> {
    buf: [T; 64],
    #[init]
    ptr: StaticUninit<*const T>,
}

Now we also need to implement a way to construct a PtrBufUninit and implement PinnedInit:

#![feature(generic_associated_types, const_ptr_offset_from, const_refs_to_cell)]
use pinned_init::prelude::*;
impl<T> PinnedInit for PtrBufUninit<T> {
    type Initialized = PtrBuf<T>;

    fn init_raw(this: NeedsPinnedInit<Self>) {
        let PtrBufOngoingInit {
            ptr,
            buf,
            end,
        } = this.begin_init();
        ptr.init(&*buf as *const T);
        end.init(buf.last().unwrap() as *const T);
    }
}

impl<T> From<[T; 64]> for PtrBufUninit<T> {
    fn from(buf: [T; 64]) -> Self {
        Self {
            buf,
            ptr: StaticUninit::uninit(),
            end: StaticUninit::uninit(),
        }
    }
}

Now implementing the next method is rather staight forward:

impl<T> PtrBuf<T> {
    pub fn next(self: Pin<&mut Self>) -> Option<&T> {
        let this = self.project();
        if **this.ptr > **this.end {
            None
        } else {
            let res = unsafe {
                // SAFETY: we were correctly initialized and checked bounds
                // so this.ptr points to somewhere in buf.
                &***this.ptr
            };
            **this.ptr = unsafe {
                // SAFETY: the resulting pointer is either one byte after buf, or
                // inside buf.
                // An offset of 1 cannot overflow, because we allocated `[T;
                // 64]` before. the allocation also does not wrap around the
                // address space.
                this.ptr.offset(1)
            };
            Some(res)
        }
    }
}

Implementing support for a custom pointer type

For a pointer to be supported by this library, it needs to implement OwnedUniquePtr<T>.

This example shows the OwnedUniquePtr implementation for Box<T>:

#![feature(generic_associated_types)]
use pinned_init::{ptr::OwnedUniquePtr, transmute::TransmuteInto};
use core::{marker::PhantomData, ops::{Deref, DerefMut}, pin::Pin};

// SAFETY:
// - Box owns its data.
// - Box knows statically to have the only pointer that points to its
// value.
// - we provided the same pointer type for `Self::Ptr`.
unsafe impl<T: ?Sized> OwnedUniquePtr<T> for Box<T> {
    type Ptr<U: ?Sized> = Box<U>;

    unsafe fn transmute_pointee_pinned<U>(this: Pin<Self>) ->
        Pin<Self::Ptr<U>>
    where
        T: TransmuteInto<U>,
    {
        unsafe {
            // SAFETY: we later repin the pointer and in between never move
            // the data behind it.
            let this = Pin::into_inner_unchecked(this);
            // this is safe, due to the requriements of this function
            let this: Box<U> = Box::from_raw(Box::into_raw(this));
            Pin::new_unchecked(this)
        }
    }
}

What problem this library solves

Normally when writing rust, the principle of RAII is used. This is fine for most applications, but when one needs to already be pinned for initalization to start, this becomes a problem. Without this library you would need to resort to unsafe for all such initializations.

For example the PtrBuf from above without this library and without pin_project could look like this:

use core::{mem::MaybeUninit, pin::Pin, ptr};

pub struct PtrBuf<T> {
    buf: [T; 64],
    ptr: *const T,
    end: *const T,
}

impl<T> PtrBuf<T> {
    /// Construct a new PtrBuf.
    ///
    /// # Safety
    ///
    /// The caller needs to call [`PtrBuf::init`] before using this PtrBuf
    pub unsafe fn new(buf: [T; 64]) -> Self {
        Self {
            buf,
            ptr: ptr::null(),
            end: ptr::null(),
        }
    }

    /// Initializes this PtrBuf
    ///
    /// # Safety
    ///
    /// The caller needs to guarantee that this function is only called
    /// once.
    pub unsafe fn init(self: Pin<&mut Self>) {
        let ptr = &self.buf as *const T;
        let end = self.buf.last().unwrap() as *const T;
        unsafe {
            // SAFETY: we do not move the data behind this pointer.
            let this = self.get_unchecked_mut();
            this.ptr = ptr;
            this.end = end;
        }
    }

    /// Fetches the next value, if present.
    ///
    /// # Safety
    /// The caller needs to call [`PtrBuf::init`] before calling this
    /// function.
    pub unsafe fn next(self: Pin<&mut Self>) -> Option<&T> {
        let this = unsafe {
            // SAFETY: We never move out of this pointer
            self.get_unchecked_mut()
        };
        debug_assert!(!this.ptr.is_null());
        if this.ptr > this.end {
            None
        } else {
            let res = unsafe {
                // SAFETY: we checked bounds before and the caller
                // guarantees, that they called `init`.
                &*this.ptr
            };
            // SAFETY: the resulting pointer is either one byte after buf, or
            // inside buf.
            // An offset of 1 cannot overflow, because we allocated `[T;
            // 64]` before. the allocation also does not wrap around the
            // address space.
            this.ptr = this.ptr.offset(1);
            Some(res)
        }
    }
}

Ugh.

The worst thing about this way of performing pinned initialization is, that users of the PtrBuf library will have to use unsafe to initialize and use PtrBuf. It is very easy to forget a call to init if one creates a PtrBuf and only later pins it. When the types are designed to be both used before and after pinning, then this becomes even more of a problem source.

Using unsafe for these invariants just results in rust code that is arguably less ergonomic the same code in C.

Modules

Custom pointer types used to ensure that initialization was done completly. The pointer types provided by this module [NeedsPinnedInit<'init, T>] and [NeedsInit<'init, T>] will

Module providing a special pointer trait used to transfer owned data and to allow safer transmuation of data without forgetting to change other pointer types.

Using MaybeUninit<T> requires unsafe, but this is often not necessary, because the type system can statically determine the initialization status.

Initializing a value in place (because it is pinned) requires a transmuation. Because transmuations are inherently unsafe, this module aims to create a safer abstraction and requires users to explicitly opt in to use this initialization.

Traits

Facilitates pinned initialization. Before you implement this trait manually, look at the pinned_init proc macro attribute, it can be used to implement this trait in a safe and sound fashion in many cases.

Sealed trait to facilitate safe initialization of the types supported by this crate.

Attribute Macros

Use this attribute on a struct with named fields to ensure safe pinned initialization of all the fields marked with #[init].

Use this attribute on a struct with named fields to ensure safer pinned initialization of all the fields marked with #[init].