Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mut ref dependents #60

Merged
merged 4 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading