Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
silvanshade committed May 5, 2024
1 parent b5d4959 commit 4ae621a
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 87 deletions.
45 changes: 34 additions & 11 deletions src/deref_move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,56 @@ use core::{mem::MaybeUninit, ops::DerefMut};

use crate::{into_move::IntoMove, move_ref::MoveRef, slot::Slot};

/// Derefencing move operations for [`MoveRef`].
///
/// This trait serves a similar purpose for [`MoveRef`] as [`Deref`](core::ops::Deref) does for
/// normal references.
///
/// In order to implement this trait, the `Self` pointer type must be the *unique owner* of its
/// referent, such that dropping `Self` would cause its referent's destructor to run.
///
/// This is a subtle condition that depends upon the semantics of the `Self` pointer type and must
/// be verified by the implementer, hence the unsafety.
///
/// Examples:
///
/// - [`MoveRef<T>`] implements [`DerefMove`] by definition.
/// - [`Box<T>`](crate::Box<T>) implements [`DerefMove`] because when it drops it destructs `T`.
/// - `&mut T` does *not* implement [`DerefMove`] because it is non-owning.
/// - [`Arc<T>`](crate::Arc<T>) does *not* implement [`DerefMove`] because it is not *uniquely*
/// owning.
/// - [`Rc<T>`](crate::Rc<T>) does *not* implement [`DerefMove`] because it is not *uniquely*
/// owning.
/// - [`Pin<P>`](core::pin::Pin<T>) given `P: DerefMove`, implements [`DerefMove`] only when
/// `P::Target: Unpin`, because `DerefMove: DerefMut` and `Pin<P>: DerefMut` requires `P::Target:
/// Unpin`.
///
/// # Safety
///
/// Correctness for `DerefMove` impls require that the uniqueness invariant
/// of [`MoveRef`] is upheld. In particular, the following function *must not*
/// violate memory safety:
/// Correctness for [`DerefMove`] impls require that the unique ownership invariant of [`MoveRef`]
/// is upheld. In particular, the following function *must not* violate memory safety:
/// ```
/// # use moveref::{DerefMove, MoveRef, bind};
/// fn move_out_of<P>(p: P) -> P::Target
/// fn move_out_of<P>(ptr: P) -> P::Target
/// where
/// P: DerefMove,
/// P::Target: Sized,
/// {
/// unsafe {
/// // Replace `p` with a move reference into it.
/// bind!(p = &move *p);
/// // Move out of `ptr` into a fresh `MoveRef` (backed by a fresh storage `Slot`).
/// bind!(mvp = &move *ptr);
///
/// // Move out of `p`. From this point on, the `P::Target` destructor must
/// // run when, and only when, the function's return value goes out of
/// // scope per the usual Rust rules.
/// // From this point on, the `P::Target` destructor must run when, and only when,
/// // the function's return value goes out of scope per the usual Rust rules.
/// //
/// // In particular, the original `p` or any pointer it came from must not
/// // In particular, the original `ptr` or any pointer it came from must not
/// // run the destructor when they go out of scope, under any circumstance.
/// MoveRef::into_inner(p)
/// MoveRef::into_inner(mvp)
/// }
/// }
/// ```
pub unsafe trait DerefMove: DerefMut + IntoMove {
/// Construct a [`MoveRef`] by dereferencing `self` and moving its contents into `storage`.
fn deref_move<'frame>(
self,
storage: Slot<'frame, Self::Storage>,
Expand Down
4 changes: 4 additions & 0 deletions src/emplace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use core::{mem::MaybeUninit, ops::Deref, pin::Pin};

use crate::new::{New, TryNew};

/// Operations for constructing [`New`] values into a `Self::Output` instance.
pub trait Emplace<T>: Sized + Deref {
type Output: Deref<Target = Self::Target>;

/// Construct a [`New`] value into a fresh `Self::Output` instance.
#[inline]
fn emplace<N: New<Output = T>>(new: N) -> Self::Output {
match Self::try_emplace(new) {
Expand All @@ -13,6 +15,8 @@ pub trait Emplace<T>: Sized + Deref {
}
}

/// Try to construct a [`New`] value into a fresh `Self::Output` instance.
///
/// # Errors
///
/// Should return `Err` if the `new` initializer fails with an error.
Expand Down
3 changes: 3 additions & 0 deletions src/into_move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ use core::{mem::MaybeUninit, ops::Deref, pin::Pin};

use crate::{deref_move::DerefMove, move_ref::MoveRef, slot::Slot};

/// A trait for transforming a [`Deref`] type into a pinned [`MoveRef`] with respect to a specified
/// backing storage type [`IntoMove::Storage`].
pub trait IntoMove: Deref + Sized {
type Storage: Sized;

/// Consume `self` and create a pinned [`MoveRef`] with `self`'s contents placed into `storage`.
fn into_move<'frame>(
self,
storage: Slot<'frame, Self::Storage>,
Expand Down
11 changes: 11 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,37 @@
#![deny(clippy::implicit_return)]
#![deny(clippy::nursery)]
#![deny(clippy::pedantic)]
#![deny(clippy::missing_docs_in_private_items)]
#![allow(clippy::needless_return)]
#![allow(clippy::redundant_pub_crate)]
#![allow(clippy::type_repetition_in_bounds)]
#![no_std]

//! Types and traits for C++ style placement initialization and move semantics.

#[cfg(feature = "alloc")]
extern crate alloc;

#[cfg(feature = "alloc")]
pub(crate) use alloc::{boxed::Box, rc::Rc, sync::Arc};

/// Macros for creating [`crate::MoveRef`] values.
#[macro_use]
mod macros;

/// Dereferencing move operations.
mod deref_move;
/// Emplacement operations for constructing values.
mod emplace;
/// Movement operations.
mod into_move;
/// Move-dereferencing uniquely-owning references.
mod move_ref;
/// Construction operations.
pub mod new;
/// Storage slots for move-references.
mod slot;
/// Storage slot implementation details.
mod slot_storage;

pub use deref_move::DerefMove;
Expand Down
95 changes: 82 additions & 13 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
/// Macro for binding a variable to a fresh [`MoveRef`](crate::MoveRef).
///
/// - `bind!(x = &move *ptr)` creates an `x: MoveRef<T>` given `ptr: impl (DerefMove +
/// DerefMut<Target = T>)`
///
/// The above invocation moves the referent of a *uniquely* owning poiner into a fresh
/// [`MoveRef`](crate::MoveRef) bound to `x`.
///
/// - `bind!(x = &move val)` creates an `x: MoveRef<T>` given `val: T`
///
/// The above invocation moves any value into a fresh [`MoveRef`](crate::MoveRef) bound to `x`.
///
/// - `bind!(x = con)` creates an `x: Pin<MoveRef<T>>` given `con: impl New<Output = T>`
///
/// The above invocaton constructs a [`New`](crate::New) value into a fresh
/// [`MoveRef`](crate::MoveRef) bound to `x`.
///
/// - `bind!(mut x: T = ...)` (with right-hand side of `&move *ptr` or `&move val` or `con`)
///
/// The above generalization can be used with any earlier invocation form to add mutability and
/// typing annotations.
#[macro_export]
macro_rules! bind {
(mut $name:ident $(: $ty:ty)? = &move *$expr:expr) => {
Expand Down Expand Up @@ -33,10 +54,40 @@ macro_rules! bind {
};
}

/// Macro for creating a fresh [`MoveRef`](crate::MoveRef) expression.
///
/// Because a `v: MoveRef<'frame, T>` always has an associated lifetime `'frame`, this macro can
/// generally only be used where it would be immediately consumed by some enclosing expression, such
/// as in the position of a function argument.
///
/// Trying to use this as `let v = expr!(...)` will not work because the lifetime `'frame` will not
/// expand to the enclosing let binding. Hence the need for the separate `bind!(v = ...)` macro.
///
/// Otherwise, the usage is generally the same as `bind!(...)`:
///
/// - `expr!(&move *ptr)` creates a `MoveRef<T>` given `ptr: impl (DerefMove +
/// DerefMut<Target = T>)`
///
/// The above invocation moves the referent of a *uniquely* owning poiner into a fresh
/// [`MoveRef`](crate::MoveRef).
///
/// - `expr!(&move val)` creates an `MoveRef<T>` given `val: T`
///
/// The above invocation moves any value into a fresh [`MoveRef`](crate::MoveRef).
///
/// - `expr!(con)` creates an `x: Pin<MoveRef<T>>` given `con: impl New<Output = T>`
///
/// The above invocaton constructs a [`New`](crate::New) value into a fresh
/// [`MoveRef`](crate::MoveRef).
#[macro_export]
macro_rules! expr {
(&move *$expr:expr) => {
$crate::DerefMove::deref_move($expr, $crate::expr_slot!(#[dropping]))
$crate::DerefMove::deref_move(
$expr,
$crate::expr_slot!(
#[dropping]
)
)
};
(&move $expr:expr) => {
$crate::expr_slot!().put($expr)
Expand All @@ -46,18 +97,19 @@ macro_rules! expr {
};
}

#[macro_export]
macro_rules! expr_slot {
(#[dropping]) => {{
let kind = $crate::SlotStorageKind::Drop;
$crate::SlotStorage::new(kind).slot()
}};
() => {{
let kind = $crate::SlotStorageKind::Keep;
$crate::SlotStorage::new(kind).slot()
}};
}

/// Macro for binding a variable to a fresh [`Slot`](crate::Slot) for storage of a
/// [`MoveRef`](crate::MoveRef).
///
/// - `bind_slot!(x)`
///
/// The above invocation binds a slot to the variable `x`.
///
/// - `bind_slot!(#[dropping] x)`
///
/// The above invocation binds a (dropping) slot to the variable `x`. A [`MoveRef`](crate::MoveRef)
/// using `x` as backing storage will drop its referent when `x` goes out of scope.
///
/// Both of the above invocation forms also allow typing annotations on `x` as with [`bind!`].
#[macro_export]
macro_rules! bind_slot {
(#[dropping] $($name:ident : $ty:ty),* $(,)?) => {
Expand Down Expand Up @@ -90,6 +142,22 @@ macro_rules! bind_slot {
};
}

/// Macro for creating a fresh [`Slot`](crate::Slot) expression.
///
/// This macro has the same relationship to [`bind_slot!`] as [`expr!`] does to [`bind!`].
#[macro_export]
macro_rules! expr_slot {
(#[dropping]) => {{
let kind = $crate::SlotStorageKind::Drop;
$crate::SlotStorage::new(kind).slot()
}};
() => {{
let kind = $crate::SlotStorageKind::Keep;
$crate::SlotStorage::new(kind).slot()
}};
}

/// Boilerplate macro for defining trivial [`CopyNew`](crate::CopyNew) instances.
macro_rules! trivial_copy {
($($ty:ty $(where [$($targs:tt)*])?),* $(,)?) => {
$(
Expand All @@ -107,6 +175,7 @@ macro_rules! trivial_copy {
}
}

/// Boilerplate macro for defining trivial [`MoveNew`](crate::CopyNew) instances.
macro_rules! trivial_move {
($($ty:ty $(where [$($targs:tt)*])?),* $(,)?) => {
$(
Expand Down
45 changes: 45 additions & 0 deletions src/move_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,46 @@ use core::{

use crate::slot_storage::SlotStorageStatus;

/// A "reference" type which *uniquely* owns its referent type `T` with respect to external storage
/// with lifetime `'frame`.
///
/// Conceptually, it has these characteristics:
///
/// - similar to `&'frame mut` because it *uniquely* references other data with lifetime `'frame`
/// - similar to `Box` because it is *owning*
///
/// The distinguishing characteristic of [`MoveRef`] from `&mut` and [`Box`](crate::Box) is how it
/// is created with a backing storage [`Slot`](crate::Slot) and how that defines its ownership of
/// the referent data, and ultimately how the backing storage is responsible for running the
/// destructor for its referent when it finally goes out of scope.
///
/// A motivating example for [`MoveRef`] is the concept of placement-initialization in C++:
///
/// Imagine we define FFI bindings for a C++ class we intend to use in Rust.
///
/// Creating instances for this class on the heap is straightforward and well understood: we can use
/// raw pointers and eventually either convert to a reference or [`Box`](crate::Box).
///
/// Creating instances for this class on the stack is more difficult. We can use
/// [`MaybeUninit`](core::mem::MaybeUninit) to create a chunk of data and initialize into that.
///
/// But we have to be particularly careful when using the result because in Rust, data moves by
/// default, rather than copies by default as in C++. So any access of the data in Rust could
/// potentially move the data out from under some expected location in C++ and cause a crash when
/// execution proceeds again in C++.
///
/// So we need a type which acts like a (mutable) reference but does not let us move simply by
/// accessing it. This would be similar to a [`Pin<&mut T>`], where the [`Pin`] prevents movement,
/// but the inner `&mut` still allows mutation.
///
/// But we also want the possibility to *actually* move the data in some cases, like we would
/// explicitly do in C++ with a move constructor or move assignment operation.
///
/// This interface is exactly what [`MoveRef`] provides, along with [`DerefMove`](crate::DerefMove).
pub struct MoveRef<'frame, T: ?Sized> {
/// The underlying mutable reference with referent stored in some external [`Slot`](crate::Slot).
pub(crate) ptr: &'frame mut T,
/// Status flags for the storage which track initialization, dropping state, and reference count.
pub(crate) status: SlotStorageStatus<'frame>,
}

Expand Down Expand Up @@ -47,6 +85,7 @@ impl<T: ?Sized> Drop for MoveRef<'_, T> {
}

impl<'frame, T: ?Sized> MoveRef<'frame, T> {
/// Create a new unchecked [`MoveRef`] from a mutable ref and [`SlotStorageStatus`].
#[inline]
pub(crate) unsafe fn new_unchecked(
ptr: &'frame mut T,
Expand All @@ -55,12 +94,18 @@ impl<'frame, T: ?Sized> MoveRef<'frame, T> {
return Self { ptr, status };
}

/// Transform a [`MoveRef<T>`] into a [`Pin<MoveRef<T>>`]. This is safe because the interface
/// for [`MoveRef`] enforces that its referent will not be implicitly moved or have its storage
/// invalidated until the [`MoveRef<T>`] (and its backing [`Slot`](crate::Slot)) is dropped.
#[must_use]
#[inline]
pub fn into_pin(self) -> Pin<Self> {
return unsafe { Pin::new_unchecked(self) }; // tarpaulin
}

/// Consume a [`Pin<Self>`] and return a raw `*mut T`. This operation inhibits destruction of
/// `T` by implicit [`Drop`] and the caller becomes responsible for eventual explicit
/// destruction and cleanup, otherwise the memory will leak.
#[inline]
#[must_use]
pub fn release(pin: Pin<Self>) -> *mut T {
Expand Down
Loading

0 comments on commit 4ae621a

Please sign in to comment.