14 unstable releases (3 breaking)
Uses new Rust 2024
new 0.4.4 | May 3, 2025 |
---|---|
0.4.3 | May 3, 2025 |
0.4.2 | Apr 24, 2025 |
0.3.2 | Apr 5, 2025 |
0.1.4 | Apr 2, 2025 |
#612 in Rust patterns
1,264 downloads per month
18KB
146 lines
Mutcy
Zero-cost mutable cyclic borrows using borrow relinquishing.
lib.rs
:
Zero-Cost Mutable Cyclic Borrows
A RefCell
-like dynamic borrowing system that permits recursive borrowing
with zero runtime overhead.
This crate implements [KeyCell] of which only a single instance can be mutably borrowed at a time using a [Key]. Simultaneous immutable borrows are permitted.
Key
is a ZST, and only a single instance can exist per thread. The only
dynamic check this library performs is upon calling Key::acquire to ensure
that there exists no other instance in the same thread. This ensures that we
can only mutably borrow one KeyCell
per thread.
Once we have a mutable borrow of a KeyCell
we can relinquish this borrow
temporarily using Key::split_rw to get
&mut Key
, and borrow other KeyCell
s. This is what allows us to implement
recursive calls with mutable access.
Comparison to RefCell
Unlike RefCell
, borrows on [KeyCell]s will never fail. They also don't
need to perform any runtime checks. All borrow checking is performed at
compile time.
- Borrowing is zero-cost.
- Borrowing will never fail or panic.
- Only a single
KeyCell
can be mutably borrowed per thread.
Alternatives
The core idea in this crate relies on acquiring mutable borrows through a unique object (the [Key]). The same idea is implemented in a few other crates.
Examples
This crate uses ro and
rw instead of borrow
and borrow_mut
to access data.
use mutcy::{Key, KeyCell, Rw};
let mut key = Key::acquire();
let kc1 = KeyCell::new(0i32, ());
let kc2 = KeyCell::new(String::new(), ());
*kc2.rw(&mut key) += "Hello";
*kc1.rw(&mut key) += 1;
*kc2.rw(&mut key) += "World";
let item1 = kc1.ro(&key);
let item2 = kc1.ro(&key);
println!("{} - {}", *item1, *item2);
With this library it's possible to define methods that take [Rw] and transfer mutability to other [KeyCell]s when needed. The compile-time borrow checker ensures that no mutable aliasing occurs.
In the following example we show how a struct can accept a self: Rw<Self>
and relinquish its own borrows to access some other
KeyCell
.
#![feature(arbitrary_self_types)]
use mutcy::{Key, KeyCell, Meta, Rw};
use std::rc::Rc;
struct MyStruct {
value: i32,
}
impl MyStruct {
fn my_function(self: Rw<Self>) {
self.value += 1;
// This relinquishes any borrows to `self`.
let (this, key) = Key::split_rw(self);
// We can now access any other KeyCell using `key`.
let mut string = this.meta().other.rw(key);
*string += "Hello world";
self.value += 1;
}
}
struct MyStructMeta {
other: Rc<KeyCell<String>>,
}
impl Meta for MyStruct {
type Data = MyStructMeta;
}
let mut key = Key::acquire();
let other = Rc::new(KeyCell::new(String::new(), ()));
let my_struct = KeyCell::new(
MyStruct { value: 0 },
MyStructMeta {
other: other.clone(),
},
);
my_struct.rw(&mut key).my_function();
println!("{}", *other.ro(&key));
println!("{}", my_struct.ro(&key).value);
Metadata
KeyCell::new takes two arguments, the primary data to wrap, and metadata. Metadata can always be accessed immutably as it does not require a [Key].
This allows us to reference and access other [KeyCell]s without having to
clone an Rc<KeyCell<_>>
out of the initial item.
For more details see [Meta].