Skip to content

Commit

Permalink
Add support for mut ref dependents (#60)
Browse files Browse the repository at this point in the history
* Add support for mut ref dependents

This has feature that has been requested multiple times by users,
#36 and
#59.

* Fix accidental std usage.

* Model unlocked -> locked via unlocked by construction

This is less code, less run-time work and
conceptually harder to miss-use. Win-win.

* Remove unnecessary lock code and switch to AtomicBool
  • Loading branch information
Voultapher authored Sep 15, 2024
1 parent 156818a commit 10f545e
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 13 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 <[email protected]>"]
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 <[email protected]>"]
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 = "../../" }
8 changes: 6 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 @@ -671,3 +673,5 @@ macro_rules! _impl_automatic_derive {
));
};
}

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

use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::mem;
use core::ptr::{drop_in_place, read, NonNull};
use core::sync::atomic::{AtomicBool, Ordering};

extern crate alloc;

Expand Down Expand Up @@ -223,3 +225,87 @@ 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: AtomicBool,
value: UnsafeCell<T>,
}

impl<T> MutBorrow<T> {
/// Constructs a new `MutBorrow`.
pub fn new(value: T) -> Self {
// Use the Rust type system to model an affine type that can only go from unlocked -> locked
// but never the other way around.
Self {
is_locked: AtomicBool::new(false),
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 {
// Ensure this function can only be called once.
// Relaxed should be fine, because only one thread could ever read `false` anyway,
// so further synchronization is pointless.
let was_locked = self.is_locked.swap(true, Ordering::Relaxed);

if was_locked {
panic!("Tried to access locked MutBorrow")
} else {
// SAFETY: `self.is_locked` starts out as locked and can never be unlocked again, 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()
}
}

// SAFETY: The reasoning why it is safe to share `MutBorrow` across threads is as follows: The
// `AtomicBool` `is_locked` ensures that only ever exactly one thread can get access to the inner
// value. In that sense it works like a critical section, that begins when `borrow_mut()` is called
// and that ends when the outer `MutBorrow` is dropped. Once one thread acquired the unique
// reference through `borrow_mut()` no other interaction with the inner value MUST ever be possible
// while the outer `MutBorrow` is alive.
unsafe impl<T: Send> Sync for MutBorrow<T> {}
2 changes: 1 addition & 1 deletion tests-extra/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 32 additions & 4 deletions tests-extra/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
#![allow(dead_code)]
#![allow(unused_imports)]

use std::fs;
use std::process::Command;
use std::cell::RefCell;
use std::rc::Rc;
use std::str;

use crossbeam_utils::thread;

use impls::impls;

use self_cell::self_cell;
use self_cell::{self_cell, MutBorrow};

#[allow(dead_code)]
struct NotSend<'a> {
Expand Down Expand Up @@ -55,6 +54,35 @@ fn not_sync() {
assert!(!impls!(NotSendCell: Sync));
}

#[test]
fn mut_borrow_traits() {
type MutBorrowString = MutBorrow<String>;
assert!(impls!(MutBorrowString: Send));
assert!(impls!(MutBorrowString: Sync));

type MutBorrowRefCellString = MutBorrow<RefCell<String>>;
assert!(impls!(MutBorrowRefCellString: Send));
assert!(impls!(MutBorrowRefCellString: Sync));

type MutBorrowRcString = MutBorrow<Rc<String>>;
assert!(!impls!(MutBorrowRcString: Send));
assert!(!impls!(MutBorrowRcString: Sync));

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

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

#[covariant]
dependent: MutStringRef,
}
);

assert!(impls!(MutBorrowStringCell: Send));
assert!(impls!(MutBorrowStringCell: Sync));
}

#[test]
#[cfg(feature = "invalid_programs")]
// Not supported by miri isolation.
Expand Down
Loading

0 comments on commit 10f545e

Please sign in to comment.