odem-rs-core 0.1.0

Core components of the ODEM-rs simulation framework
//! The `once` module provides the wrapper type [`Lease`] that allows extending
//! the lifetime of references to cover the whole remaining time of the
//! referenced value.
//!
//! This effectively allows a weaker form of pinning and changes mutable borrows
//! to ownership semantics without having to move the value.
//!
//! Credit goes to **Lukas Markeffsky** for the idea and proof-of-concept using
//! the underlying mechanism of using an inner lifetime to have the
//! borrow-checker enforce pinning and ownership semantics for mutable borrows.

use core::{
	marker::PhantomData,
	ops::{Deref, DerefMut},
	pin::Pin,
};

/// A "one-shot" reference wrapper that pins borrows in place once they have
/// been passed into a function as an argument. For `mut` borrows, this changes
/// them to behave similarly to an owned value, preventing subsequent borrows
/// once they have been consumed in a function.
///
/// `Lease` is a lightweight wrapper around a value of type `T` that is
/// associated with a lifetime `'p`. Once borrowed and moved into a function
/// (by passing it as a parameter), it can no longer be moved or exclusively
/// borrowed again in the original scope, mimicking ownership behavior. This
/// helps prevent multiple borrows after a `Lease` borrow is consumed.
///
/// ## Examples
/// The purpose of this type is to enable the following pattern:
///
/// ```compile_fail,E0499
/// # use odem_rs_core::ptr::Lease;
/// fn indefinitely_borrow<'p,T>(value: &'p mut Lease<'p,T>) {
///     // ...
/// }
///
/// struct S;
/// let mut value = Lease::new(S);
/// indefinitely_borrow(&mut value);
/// indefinitely_borrow(&mut value); // error: value is mutably borrowed
/// ```
///
/// It also works with shared references, effectively preventing the value to
/// be moved after its first borrow:
///
/// ```compile_fail,E0505
/// # use odem_rs_core::ptr::Lease;
/// fn indefinitely_borrow<'p,T>(value: &'p Lease<'p,T>) {
///     // ...
/// }
///
/// struct S;
/// let mut value = Lease::new(S);
/// indefinitely_borrow(&value);
/// indefinitely_borrow(&value); // allowed
/// std::mem::forget(value);  // error: value is still borrowed
/// ```
///
/// There are type-aliases for these two use-cases to help phrase them more
/// succinctly:
///
/// ```
/// # use odem_rs_core::ptr::{Lease, LeasedRef, LeasedMut};
/// fn indefinitely_borrow_ref<T>(value: LeasedRef<T>) {
///     // same semantics as before but inferred lifetime
/// }
///
/// fn indefinitely_borrow_mut<T>(value: LeasedMut<T>) {
///     // same semantics as before but inferred lifetime
/// }
/// ```
///
/// This is *almost* as good as pinning the value but doesn't guarantee that
/// `drop` is executed, which pinning does:
///
/// ```
/// # use {odem_rs_core::ptr::{Lease, LeasedRef}, core::mem::ManuallyDrop};
/// fn indefinitely_borrow<T>(value: LeasedRef<T>) {
///     // ...
/// }
///
/// struct S;
/// impl Drop for S {
///     fn drop(&mut self) {
///         assert!(false, "drop doesn't run");
///     }
/// }
///
/// let mut value = ManuallyDrop::new(Lease::new(S));
/// indefinitely_borrow(&value); // borrowed forever
/// // no `drop` is run due to the use of `ManuallyDrop`
/// ```
///
/// Pinning a `Lease` ensures that the destructor is run by the [drop guarantee].
/// This allows use of the following pattern:
///
/// ```compile_fail,E0499
/// # use {core::pin::{Pin, pin}, odem_rs_core::ptr::{Lease, LeasedMut}};
/// fn pinned_borrow<T>(value: Pin<LeasedMut<T>>) {
///    // ...
/// }
///
/// struct S;
/// impl Drop for S {
///     fn drop(&mut self) {
///         println!("drop runs");
///     }
/// }
///
/// let mut value = pin!(Lease::new(S));
/// pinned_borrow(value.as_mut());
/// pinned_borrow(value.as_mut()); // error: value is mutably borrowed
/// // `drop` runs because `Pin<&mut T>` is different from `Pin<&mut ManuallyDrop<T>>`
/// ```
///
/// # How it works
///
/// To the borrow-checker, `Lease<'p,T>` stores a reference to `T` with an
/// invariant `'p` lifetime. This makes `'p` invariant over `Lease` per
/// [this table] and has the following two consequences:
///
/// 1. `Lease` cannot implement `Drop` because `Drop::drop` takes a `mut`
///    reference that definitionally cannot coexist with another active
///    reference to one of its members.
/// 2. Any reference - shared or exclusive - borrows the `Lease` indefinitely,
///    which enforces the intended "one-shot" borrow semantics.
///
/// The second point is a consequence of the invariance of `'p` by the
/// following argument:
/// 
/// 1. Choosing a `'p` that lives longer than the thing it's referencing leads
///    to a dangling reference, because `&'long Lease<'short, T>` cannot exist.
/// 2. Choosing a `'p` that lives shorter, as in `&'short Lease<'long, T>` can
///    only exist if `'long` can be coerced to `'short` in order to arrive at
///    the `&'short Lease<'short, T>` required by the function signature.  
///    This is only allowed if `Lease<'p, T>` is covariant over `'p`.
/// 
/// `'p` being invariant over `Lease<'p, T>` disallows exactly that, forcing the
/// borrow-checker to select the shorter of the two lifetimes for both the outer
/// and the inner `'p`. This however forces the user to borrow the `Lease`
/// **exactly** for the duration of its lifetime, since its lifetime shortens
/// with `'p`.
///
/// Credit goes to **Lukas Markeffsky** for the idea and proof-of-concept using
/// the underlying mechanism of using an inner lifetime to have the
/// borrow-checker enforce pinning and ownership semantics for mutable borrows.
/// 
/// [drop guarantee]: core::pin
/// [this table]: https://doc.rust-lang.org/nomicon/subtyping.html#variance
#[derive(Debug, Clone, Copy, Default)]
#[repr(transparent)]
pub struct Lease<'p, T: ?Sized> {
	/// Marker to mutably borrow from `Self`, making lifetime `'p` invariant.
	_mark: PhantomData<&'p mut Self>,
	/// The actual wrapped value of type `T`.
	///
	/// This type can implement `Drop` as usual, since it doesn't borrow with
	/// lifetime `'p`.
	///
	/// This value is treated as if it has an exclusive reference with lifetime
	/// `'p`, but the `Lease` wrapper prevents further borrows after the lifetime
	/// is fixed due to the value being passed to a function.
	value: T,
}

/// A type alias representing a shared reference to a [`Lease`].
///
/// # Overview
///
/// `LeasedRef<'p,T>` is `&'p Lease<'p, T>` and cannot be moved after being passed
/// into a function.
///
/// # Example
///
/// ```compile_fail,E0505
/// # use odem_rs_core::ptr::{Lease, LeasedRef};
/// fn ref_it<T>(val: LeasedRef<T>) {
///     // ...
/// }
///
/// struct S;
/// let x = Lease::new(S);
/// ref_it(&x);
/// ref_it(&x); // allowed as often as you want
/// std::mem::forget(x); // error: value is still borrowed
/// ```
pub type LeasedRef<'p, T> = &'p Lease<'p, T>;

/// A type alias representing a mutable reference to a [`Lease`].
///
/// # Overview
///
/// `LeasedMut<'p,T>` is `&'p mut Lease<'p, T>` and cannot be moved or accessed
/// (other than during `drop`) after being passed into a function, as if
/// ownership has been transferred. The resulting value still lives in the scope
/// of the function caller, i.e. it outlives the function scope.
///
/// # Example
///
/// ```compile_fail
/// # use odem_rs_core::ptr::{Lease, LeasedMut};
/// fn ref_it<T>(val: LeasedMut<T>) {
///     // ...
/// }
///
/// struct S;
/// let mut x = Lease::new(S);
/// ref_it(&mut x);
/// ref_it(&mut x);      // Error: value is still mutably borrowed
/// std::mem::forget(x); // error: value is still borrowed
/// ```
pub type LeasedMut<'p, T> = &'p mut Lease<'p, T>;

impl<T> Lease<'_, T> {
	/// Creates a new `Lease` wrapping the given `value`.
	pub const fn new(value: T) -> Self {
		Self {
			_mark: PhantomData,
			value,
		}
	}

	/// Consumes the `Lease`, returning the wrapped value.
	///
	/// This method takes ownership of `self` and can only be called before
	/// a reference to the `LeasedRef` has been moved into a function.
	pub fn into_inner(self) -> T {
		self.value
	}
}

impl<'p, T: ?Sized> Lease<'p, T> {
	/// Projects a pinned `Lease` to a pinned mutable reference to the underlying
	/// `T`.
	///
	/// # Examples
	///
	/// ```
	/// # use odem_rs_core::ptr::{LeasedMut};
	/// use std::pin::Pin;
	///
	/// fn do_something_with_pin<T>(mut pinned_ref: Pin<LeasedMut<T>>) {
	///     let _: Pin<&mut T> = pinned_ref.as_mut().project();
	///     // ...
	/// }
	/// ```
	pub fn project(self: Pin<&mut Self>) -> Pin<&mut T> {
		// SAFETY: It's safe to map `&mut LeasedRef<'p, T>` to `&mut T`
		// because LeasedMut cannot be moved out of after being pinned.
		unsafe { self.map_unchecked_mut(|this| &mut this.value) }
	}

	/// Projects the shared reference to a `Lease` from a type to one of its
	/// members.
	pub fn map_pin<F, R>(self: Pin<&'p Self>, f: F) -> Pin<&'p Lease<'p, R>>
	where
		F: FnOnce(Pin<&T>) -> Pin<&R>,
	{
		// SAFETY: This mapping doesn't violate any pinning guarantees.
		let value = f(unsafe { self.map_unchecked(|inner| &inner.value) });

		// SAFETY: It is safe to transfer the outer `Lease` onto the inner one,
		// because
		unsafe { core::mem::transmute::<Pin<&R>, Pin<LeasedRef<'_, R>>>(value) }
	}

	/// Projects the mutable reference to a `Lease` from a type to one of its
	/// members.
	pub fn map_pin_mut<F, R>(self: Pin<&'p mut Self>, f: F) -> Pin<&'p mut Lease<'p, R>>
	where
		F: FnOnce(Pin<&mut T>) -> Pin<&mut R>,
	{
		let value = f(unsafe { self.map_unchecked_mut(|inner| &mut inner.value) });
		unsafe { core::mem::transmute::<Pin<&mut R>, Pin<LeasedMut<'_, R>>>(value) }
	}
}

impl<T: ?Sized> Deref for Lease<'_, T> {
	type Target = T;

	fn deref(&self) -> &Self::Target {
		&self.value
	}
}

impl<T: ?Sized> DerefMut for Lease<'_, T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.value
	}
}