Skip to content

Commit

Permalink
Add support for mut ref dependents
Browse files Browse the repository at this point in the history
This has feature that has been requested multiple times by users,
#36 and
#59.
  • Loading branch information
Voultapher committed Sep 6, 2024
1 parent 156818a commit cb88ca8
Show file tree
Hide file tree
Showing 10 changed files with 453 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ impl Debug for NewStructName { ... }

Self-referential structs are currently not supported with safe vanilla Rust. The
only reasonable safe alternative is to expect the user to juggle 2 separate data
structures which is a mess. The library solution ouroboros is really expensive
to compile due to its use of procedural macros.
structures which is a mess. The library solution ouroboros is expensive to
compile due to its use of procedural macros.

This alternative is `no_std`, uses no proc-macros, some self contained unsafe
and works on stable Rust, and is miri tested. With a total of less than 300
Expand Down
3 changes: 2 additions & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ members = [
"fallible_dependent_construction",
"lazy_ast",
"owner_with_lifetime",
]
"mut_ref_to_owner_in_builder",
]
2 changes: 2 additions & 0 deletions examples/REAME.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ The advanced examples:

- [How to build a lazy AST with self_cell](lazy_ast)

- [How to handle dependents that take a mutable reference](mut_ref_to_owner_in_builder)

- [How to use an owner type with lifetime](owner_with_lifetime)

10 changes: 10 additions & 0 deletions examples/mut_ref_to_owner_in_builder/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "owner_with_lifetime"
version = "0.1.0"
authors = ["Lukas Bergdoll <lukas.bergdoll@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
self_cell = { path="../../"}
14 changes: 14 additions & 0 deletions examples/mut_ref_to_owner_in_builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# `mut_ref_to_owner_in_builder` Example

This example shows how to handle dependent types that want to reference the
owner by `&mut`. This works by using the wrapper type `MutBorrow` around the
owner type. This allows us to call `borrow_mut` in the builder function. This
example also shows how to recover the owner value if desired.

Run this example with `cargo run`, it should output:

```
dependent before pop: abc
dependent after pop: ab
recovered owner: ab
```
25 changes: 25 additions & 0 deletions examples/mut_ref_to_owner_in_builder/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use self_cell::{self_cell, MutBorrow};

type MutStringRef<'a> = &'a mut String;

self_cell!(
struct MutStringCell {
owner: MutBorrow<String>,

#[covariant]
dependent: MutStringRef,
}
);

fn main() {
let mut cell = MutStringCell::new(MutBorrow::new("abc".into()), |owner| owner.borrow_mut());

cell.with_dependent_mut(|_owner, dependent| {
println!("dependent before pop: {}", dependent);
dependent.pop();
println!("dependent after pop: {}", dependent);
});

let recovered_owner: String = cell.into_owner().into_inner();
println!("recovered owner: {}", recovered_owner);
}
4 changes: 2 additions & 2 deletions examples/owner_with_lifetime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "owner_with_lifetime"
name = "mut_ref_to_owner_in_builder"
version = "0.1.0"
authors = ["Lukas Bergdoll <lukas.bergdoll@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
self_cell = { path="../../"}
self_cell = { path = "../../" }
57 changes: 55 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
//!
//! Self-referential structs are currently not supported with safe vanilla Rust.
//! The only reasonable safe alternative is to have the user juggle 2 separate
//! data structures which is a mess. The library solution ouroboros is really
//! expensive to compile due to its use of procedural macros.
//! data structures which is a mess. The library solution ouroboros is expensive
//! to compile due to its use of procedural macros.
//!
//! This alternative is `no_std`, uses no proc-macros, some self contained
//! unsafe and works on stable Rust, and is miri tested. With a total of less
Expand Down Expand Up @@ -138,6 +138,8 @@
//! - [How to build a lazy AST with
//! self_cell](https://github.com/Voultapher/self_cell/tree/main/examples/lazy_ast)
//!
//! - [How to handle dependents that take a mutable reference](https://github.com/Voultapher/self_cell/tree/main/examples/mut_ref_to_owner_in_builder) see also [`MutBorrow`]
//!
//! - [How to use an owner type with
//! lifetime](https://github.com/Voultapher/self_cell/tree/main/examples/owner_with_lifetime)
//!
Expand Down Expand Up @@ -352,6 +354,9 @@ macro_rules! self_cell {
) -> Self {
use ::core::ptr::NonNull;

#[allow(unused)]
use $crate::unsafe_self_cell::MutBorrowDefaultTrait;

unsafe {
// All this has to happen here, because there is not good way
// of passing the appropriate logic into UnsafeSelfCell::new
Expand Down Expand Up @@ -380,6 +385,8 @@ macro_rules! self_cell {
// Move owner into newly allocated space.
owner_ptr.write(owner);

$crate::_mut_borrow_unlock!(owner_ptr, $Owner);

// Drop guard that cleans up should building the dependent panic.
let drop_guard =
$crate::unsafe_self_cell::OwnerAndCellDropGuard::new(joined_ptr);
Expand All @@ -388,6 +395,8 @@ macro_rules! self_cell {
dependent_ptr.write(dependent_builder(&*owner_ptr));
::core::mem::forget(drop_guard);

$crate::_mut_borrow_lock!(owner_ptr, $Owner);

Self {
unsafe_self_cell: $crate::unsafe_self_cell::UnsafeSelfCell::new(
joined_void_ptr,
Expand All @@ -407,6 +416,9 @@ macro_rules! self_cell {
) -> ::core::result::Result<Self, Err> {
use ::core::ptr::NonNull;

#[allow(unused)]
use $crate::unsafe_self_cell::MutBorrowDefaultTrait;

unsafe {
// See fn new for more explanation.

Expand All @@ -425,6 +437,8 @@ macro_rules! self_cell {
// Move owner into newly allocated space.
owner_ptr.write(owner);

$crate::_mut_borrow_unlock!(owner_ptr, $Owner);

// Drop guard that cleans up should building the dependent panic.
let mut drop_guard =
$crate::unsafe_self_cell::OwnerAndCellDropGuard::new(joined_ptr);
Expand All @@ -434,6 +448,8 @@ macro_rules! self_cell {
dependent_ptr.write(dependent);
::core::mem::forget(drop_guard);

$crate::_mut_borrow_lock!(owner_ptr, $Owner);

::core::result::Result::Ok(Self {
unsafe_self_cell: $crate::unsafe_self_cell::UnsafeSelfCell::new(
joined_void_ptr,
Expand All @@ -456,6 +472,9 @@ macro_rules! self_cell {
) -> ::core::result::Result<Self, ($Owner, Err)> {
use ::core::ptr::NonNull;

#[allow(unused)]
use $crate::unsafe_self_cell::MutBorrowDefaultTrait;

unsafe {
// See fn new for more explanation.

Expand All @@ -474,6 +493,8 @@ macro_rules! self_cell {
// Move owner into newly allocated space.
owner_ptr.write(owner);

$crate::_mut_borrow_unlock!(owner_ptr, $Owner);

// Drop guard that cleans up should building the dependent panic.
let mut drop_guard =
$crate::unsafe_self_cell::OwnerAndCellDropGuard::new(joined_ptr);
Expand All @@ -483,6 +504,8 @@ macro_rules! self_cell {
dependent_ptr.write(dependent);
::core::mem::forget(drop_guard);

$crate::_mut_borrow_lock!(owner_ptr, $Owner);

::core::result::Result::Ok(Self {
unsafe_self_cell: $crate::unsafe_self_cell::UnsafeSelfCell::new(
joined_void_ptr,
Expand Down Expand Up @@ -671,3 +694,33 @@ macro_rules! _impl_automatic_derive {
));
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! _mut_borrow_unlock {
($owner_ptr:expr, $Owner:ty) => {{
let wrapper = std::mem::transmute::<
&mut $Owner,
&mut $crate::unsafe_self_cell::MutBorrowSpecWrapper<$Owner>,
>(&mut *$owner_ptr);

// If `T` is `MutBorrow` will call `unlock`, otherwise a no-op.
wrapper.unlock();
}};
}

#[doc(hidden)]
#[macro_export]
macro_rules! _mut_borrow_lock {
($owner_ptr:expr, $Owner:ty) => {{
let wrapper = std::mem::transmute::<
&$Owner,
&$crate::unsafe_self_cell::MutBorrowSpecWrapper<$Owner>,
>(&*$owner_ptr);

// If `T` is `MutBorrow` will call `lock`, otherwise a no-op.
wrapper.lock();
}};
}

pub use unsafe_self_cell::MutBorrow;
124 changes: 124 additions & 0 deletions src/unsafe_self_cell.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(clippy::needless_lifetimes)]

use core::cell::{Cell, UnsafeCell};
use core::marker::PhantomData;
use core::mem;
use core::ptr::{drop_in_place, read, NonNull};
Expand Down Expand Up @@ -223,3 +224,126 @@ impl<Owner, Dependent> JoinedCell<Owner, Dependent> {
(owner_ptr, dependent_ptr)
}
}

/// Wrapper type that allows creating a self-referential type that hold a mutable borrow `&mut T`.
///
/// Example usage:
///
/// ```
/// use self_cell::{self_cell, MutBorrow};
///
/// type MutStringRef<'a> = &'a mut String;
///
/// self_cell!(
/// struct MutStringCell {
/// owner: MutBorrow<String>,
///
/// #[covariant]
/// dependent: MutStringRef,
/// }
/// );
///
/// let mut cell = MutStringCell::new(MutBorrow::new("abc".into()), |owner| owner.borrow_mut());
/// cell.with_dependent_mut(|_owner, dependent| {
/// assert_eq!(dependent, &"abc");
/// dependent.pop();
/// assert_eq!(dependent, &"ab");
/// });
///
/// let recovered_owner: String = cell.into_owner().into_inner();
/// assert_eq!(recovered_owner, "ab");
/// ```
pub struct MutBorrow<T> {
// Private on purpose.
is_locked: Cell<bool>,
value: UnsafeCell<T>,
}

impl<T> MutBorrow<T> {
/// Constructs a new `MutBorrow`.
pub fn new(value: T) -> Self {
Self {
is_locked: Cell::new(true),
value: UnsafeCell::new(value),
}
}

/// Obtains a mutable reference to the underlying data.
///
/// This function can only sensibly be used in the builder function. Afterwards, it's impossible
/// to access the inner value, with the exception of [`MutBorrow::into_inner`].
///
/// # Panics
///
/// Will panic if called anywhere but in the dependent constructor. Will also panic if called
/// more than once.
pub fn borrow_mut(&self) -> &mut T {
if self.is_locked.get() {
panic!("Tried to access locked MutBorrow")
} else {
// Ensure this function can only be called once.
self.is_locked.set(true);

// SAFETY: `self.is_locked` starts out as locked and can only be unlocked with `unsafe`
// functions, which guarantees that this function can only be called once. And the
// `self.value` being private ensures that there are no other references to it.
unsafe { &mut *self.value.get() }
}
}

/// Consumes `self` and returns the wrapped value.
pub fn into_inner(self) -> T {
self.value.into_inner()
}
}

// Provides the no-op for types that are not `MutBorrow`.
#[doc(hidden)]
pub unsafe trait MutBorrowDefaultTrait {
#[doc(hidden)]
unsafe fn unlock(&mut self) {}

#[doc(hidden)]
fn lock(&self) {}
}

unsafe impl<T> MutBorrowDefaultTrait for T {}

#[doc(hidden)]
#[repr(transparent)]
pub struct MutBorrowSpecWrapper<T>(T);

impl<T: MutBorrowSpecImpl> MutBorrowSpecWrapper<T> {
#[doc(hidden)]
pub unsafe fn unlock(&mut self) {
<T as MutBorrowSpecImpl>::unlock(&mut self.0);
}

#[doc(hidden)]
pub fn lock(&self) {
<T as MutBorrowSpecImpl>::lock(&self.0);
}
}

// unsafe trait to make sure users can't impl this without unsafe.
#[doc(hidden)]
pub unsafe trait MutBorrowSpecImpl {
#[doc(hidden)]
unsafe fn unlock(&mut self);

#[doc(hidden)]
fn lock(&self);
}

unsafe impl<T> MutBorrowSpecImpl for MutBorrow<T> {
// SAFETY: The caller MUST only ever call this once.
#[doc(hidden)]
unsafe fn unlock(&mut self) {
self.is_locked.set(false);
}

#[doc(hidden)]
fn lock(&self) {
self.is_locked.set(true);
}
}
Loading

0 comments on commit cb88ca8

Please sign in to comment.