Crate moveit[][src]

A library for safe, in-place construction of Rust (and C++!) objects.

How It Works

moveit revolves around unsafe traits that impose additional guarantees on !Unpin types, such that they can be moved in the C++ sense. There are two senses of “move” frequently used:

  • The Rust sense, which is a blind memcpy and analogous-ish to the C++ “std::is_trivially_moveable` type-trait. Rust moves also render the moved-from object inaccessible.
  • The C++ sense, where a move is really like a mutating Clone operation, which leave the moved-from value accessible to be destroyed at the end of the scope.

C++ also has constructors, which are special functions that produce a new value in a particular location. In particular, C++ constructors may assume that the address of *this will not change; all C++ objects are effectively pinned and new objects must be constructed using copy or move constructors.

The Ctor, CopyCtor, and MoveCtor traits bring these concepts into Rust. A Ctor is like a nilary FnOnce, except that instead of returning its result, it writes it to a Pin<&mut MaybeUninit<T>>, which is in the “memory may be repurposed” state described in the Pin documentation (i.e., either it is freshly allocated or the destructor was recently run). This allows a Ctor to rely on the pointer’s address remaining stable, much like *this in C++.

Types that implement CopyCtor may be copy-constructed: given any pointer to T: CopyCtor, we can generate a constructor that constructs a new, identical T at a designated location. MoveCtor types may be move-constructed: given an owning pointer (see DerefMove) to T, we can generate a similar constructor, except that it also destroys the T and the owning pointer’s storage.

None of this violates the existing Pin guarantees: moving out of a Pin<P> does not perform a move in the Rust sense, but rather in the C++ sense: it mutates through the pinned pointer in a safe manner to construct a new P::Target, and then destroys the pointer and its contents.

In general, move-constructible types are going to want to be !Unpin so that they can be self-referential. Self-referential types are one of the primary motivations for move constructors.

Constructors

A constructor is any type that implements Ctor. Constructors are like closures that have guaranteed RVO, which can be used to construct a self-referential type in-place. To use the example from the Pin<T> docs:

use std::marker::PhantomPinned;
use std::mem::MaybeUninit;
use std::pin::Pin;
use std::ptr;
use std::ptr::NonNull;

use moveit::ctor;
use moveit::ctor::Ctor;
use moveit::emplace;

// This is a self-referential struct because the slice field points to the
// data field. We cannot inform the compiler about that with a normal
// reference, as this pattern cannot be described with the usual borrowing
// rules. Instead we use a raw pointer, though one which is known not to be
// null, as we know it's pointing at the string.
struct Unmovable {
  data: String,
  slice: NonNull<String>,
  _pin: PhantomPinned,
}

impl Unmovable {
  // Defer construction until the final location is known.
  fn new(data: String) -> impl Ctor<Output = Self> {
    unsafe {
      ctor::from_placement_fn(|dest| {
        let mut inner = Pin::into_inner_unchecked(dest);
        *inner = MaybeUninit::new( Unmovable {
            data,
            // We only create the pointer once the data is in place
            // otherwise it will have already moved before we even started.
            slice: NonNull::dangling(),
            _pin: PhantomPinned,
        });
        let mut inner = &mut *inner.as_mut_ptr();
        inner.slice = NonNull::from(&inner.data);
      })
    }
  }
}

// The constructor can't be used directly, and needs to be emplaced.
emplace! {
  let unmoved = Unmovable::new("hello".to_string());
}
// The pointer should point to the correct location,
// so long as the struct hasn't moved.
// Meanwhile, we are free to move the pointer around.
let mut still_unmoved = unmoved;
assert_eq!(still_unmoved.slice, NonNull::from(&still_unmoved.data));

// Since our type doesn't implement Unpin, this will fail to compile:
// let mut new_unmoved = Unmovable::new("world".to_string());
// std::mem::swap(&mut *still_unmoved, &mut *new_unmoved);

// However, we can implement `MoveCtor` to allow it to be "moved" again.

The ctor module provides various helpers for making constructors. As a rule, functions which, in Rust, would normally construct and return a value should return impl Ctor instead. This is analogous to have async fns and .iter() functions work.

In the future, we may provide a #[ctor] macro for streamlining Ctor definition.

Emplacement

The example above makes use of the emplace! macro, one of many ways to turn a constructor into a value. moveit gives you two choices for running a constructor:

  • On the stack, using the StackBox type (this is what emplace! generates).
  • On the heap, using the extension methods from the Emplace trait.

For example, we could have placed the above in a Box by writing Box::emplace(Unmovable::new()).

Re-exports

pub use crate::ctor::CopyCtor;
pub use crate::ctor::Ctor;
pub use crate::ctor::Emplace;
pub use crate::ctor::MoveCtor;
pub use crate::ctor::TryCtor;
pub use crate::stackbox::Slot;
pub use crate::stackbox::StackBox;

Modules

ctor

In-place constructors.

stackbox

Stack-based owning pointers, for emplacement on the stack.

unique

Unique-owner utilities.

Macros

emplace

Emplace a Ctor into a StackBox.

slot

Constructs a new Slot.

stackbox

Constructs a StackBox.