Crate pin_init

Source
Expand description

Library to safely and fallibly initialize pinned structs using in-place constructors.

Pinning is Rust’s way of ensuring data does not move.

It also allows in-place initialization of big structs that would otherwise produce a stack overflow.

This library’s main use-case is in Rust-for-Linux. Although this version can be used standalone.

There are cases when you want to in-place initialize a struct. For example when it is very big and moving it from the stack is not an option, because it is bigger than the stack itself. Another reason would be that you need the address of the object to initialize it. This stands in direct conflict with Rust’s normal process of first initializing an object and then moving it into it’s final memory location. For more information, see https://rust-for-linux.com/the-safe-pinned-initialization-problem.

This library allows you to do in-place initialization safely.

§Nightly Needed for alloc feature

This library requires the allocator_api unstable feature when the alloc feature is enabled and thus this feature can only be used with a nightly compiler. When enabling the alloc feature, the user will be required to activate allocator_api as well.

The feature is enabled by default, thus by default pin-init will require a nightly compiler. However, using the crate on stable compilers is possible by disabling alloc. In practice this will require the std feature, because stable compilers have neither Box nor Arc in no-std mode.

§Overview

To initialize a struct with an in-place constructor you will need two things:

  • an in-place constructor,
  • a memory location that can hold your struct (this can be the stack, an Arc<T>, Box<T> or any other smart pointer that supports this library).

To get an in-place constructor there are generally three options:

  • directly creating an in-place constructor using the pin_init! macro,
  • a custom function/macro returning an in-place constructor provided by someone else,
  • using the unsafe function pin_init_from_closure() to manually create an initializer.

Aside from pinned initialization, this library also supports in-place construction without pinning, the macros/types/functions are generally named like the pinned variants without the pin_ prefix.

§Examples

Throughout the examples we will often make use of the CMutex type which can be found in ../examples/mutex.rs. It is essentially a userland rebuild of the struct mutex type from the Linux kernel. It also uses a wait list and a basic spinlock. Importantly the wait list requires it to be pinned to be locked and thus is a prime candidate for using this library.

§Using the pin_init! macro

If you want to use PinInit, then you will have to annotate your struct with #[pin_data]. It is a macro that uses #[pin] as a marker for structurally pinned fields. After doing this, you can then create an in-place constructor via pin_init!. The syntax is almost the same as normal struct initializers. The difference is that you need to write <- instead of : for fields that you want to initialize in-place.

use pin_init::{pin_data, pin_init, InPlaceInit};

#[pin_data]
struct Foo {
    #[pin]
    a: CMutex<usize>,
    b: u32,
}

let foo = pin_init!(Foo {
    a <- CMutex::new(42),
    b: 24,
});

foo now is of the type impl PinInit<Foo>. We can now use any smart pointer that we like (or just the stack) to actually initialize a Foo:

let foo: Result<Pin<Box<Foo>>, AllocError> = Box::pin_init(foo);

For more information see the pin_init! macro.

§Using a custom function/macro that returns an initializer

Many types that use this library supply a function/macro that returns an initializer, because the above method only works for types where you can access the fields.

let mtx: Result<Pin<Arc<CMutex<usize>>>, _> = Arc::pin_init(CMutex::new(42));

To declare an init macro/function you just return an impl PinInit<T, E>:

#[pin_data]
struct DriverData {
    #[pin]
    status: CMutex<i32>,
    buffer: Box<[u8; 1_000_000]>,
}

impl DriverData {
    fn new() -> impl PinInit<Self, Error> {
        try_pin_init!(Self {
            status <- CMutex::new(0),
            buffer: Box::init(pin_init::zeroed())?,
        }? Error)
    }
}

§Manual creation of an initializer

Often when working with primitives the previous approaches are not sufficient. That is where pin_init_from_closure() comes in. This unsafe function allows you to create a impl PinInit<T, E> directly from a closure. Of course you have to ensure that the closure actually does the initialization in the correct way. Here are the things to look out for (we are calling the parameter to the closure slot):

  • when the closure returns Ok(()), then it has completed the initialization successfully, so slot now contains a valid bit pattern for the type T,
  • when the closure returns Err(e), then the caller may deallocate the memory at slot, so you need to take care to clean up anything if your initialization fails mid-way,
  • you may assume that slot will stay pinned even after the closure returns until drop of slot gets called.
use pin_init::{pin_data, pinned_drop, PinInit, PinnedDrop, pin_init_from_closure};
use core::{
    ptr::addr_of_mut,
    marker::PhantomPinned,
    cell::UnsafeCell,
    pin::Pin,
    mem::MaybeUninit,
};
mod bindings {
    #[repr(C)]
    pub struct foo {
        /* fields from C ... */
    }
    extern "C" {
        pub fn init_foo(ptr: *mut foo);
        pub fn destroy_foo(ptr: *mut foo);
        #[must_use = "you must check the error return code"]
        pub fn enable_foo(ptr: *mut foo, flags: u32) -> i32;
    }
}

/// # Invariants
///
/// `foo` is always initialized
#[pin_data(PinnedDrop)]
pub struct RawFoo {
    #[pin]
    _p: PhantomPinned,
    #[pin]
    foo: UnsafeCell<MaybeUninit<bindings::foo>>,
}

impl RawFoo {
    pub fn new(flags: u32) -> impl PinInit<Self, i32> {
        // SAFETY:
        // - when the closure returns `Ok(())`, then it has successfully initialized and
        //   enabled `foo`,
        // - when it returns `Err(e)`, then it has cleaned up before
        unsafe {
            pin_init_from_closure(move |slot: *mut Self| {
                // `slot` contains uninit memory, avoid creating a reference.
                let foo = addr_of_mut!((*slot).foo);
                let foo = UnsafeCell::raw_get(foo).cast::<bindings::foo>();

                // Initialize the `foo`
                bindings::init_foo(foo);

                // Try to enable it.
                let err = bindings::enable_foo(foo, flags);
                if err != 0 {
                    // Enabling has failed, first clean up the foo and then return the error.
                    bindings::destroy_foo(foo);
                    Err(err)
                } else {
                    // All fields of `RawFoo` have been initialized, since `_p` is a ZST.
                    Ok(())
                }
            })
        }
    }
}

#[pinned_drop]
impl PinnedDrop for RawFoo {
    fn drop(self: Pin<&mut Self>) {
        // SAFETY: Since `foo` is initialized, destroying is safe.
        unsafe { bindings::destroy_foo(self.foo.get().cast::<bindings::foo>()) };
    }
}

For more information on how to use pin_init_from_closure(), take a look at the uses inside the kernel crate. The sync module is a good starting point.

Macros§

assert_pinned
Asserts that a field on a struct using #[pin_data] is marked with #[pin] ie. that it is structurally pinned.
init
Construct an in-place initializer for structs.
pin_init
Construct an in-place, pinned initializer for structs.
stack_pin_init
Initialize and pin a type directly on the stack.
stack_try_pin_init
Initialize and pin a type directly on the stack.
try_init
Construct an in-place fallible initializer for structs.
try_pin_init
Construct an in-place, fallible pinned initializer for structs.

Structs§

ChainInit
An initializer returned by Init::chain.
ChainPinInit
An initializer returned by PinInit::pin_chain.

Traits§

InPlaceInit
Smart pointer that can initialize memory in-place.
InPlaceWrite
Smart pointer containing uninitialized memory and that can write a value.
Init
An initializer for T.
PinInit
A pin-initializer for the type T.
PinnedDrop
Trait facilitating pinned destruction.
Zeroable
Marker trait for types that can be initialized by writing just zeroes.
ZeroableOption
Marker trait for types that allow Option<Self> to be set to all zeroes in order to write None to that location.

Functions§

init_array_from_fn
Initializes an array by initializing each element via the provided initializer.
init_from_closure
Creates a new Init<T, E> from the given closure.
pin_init_array_from_fn
Initializes an array by initializing each element via the provided initializer.
pin_init_from_closure
Creates a new PinInit<T, E> from the given closure.
uninit
An initializer that leaves the memory uninitialized.
zeroed
Create a new zeroed T.

Attribute Macros§

pin_data
Used to specify the pinning information of the fields of a struct.
pinned_drop
Used to implement PinnedDrop safely.

Derive Macros§

Zeroable
Derives the Zeroable trait for the given struct.