#borrow #zero-cost #mutable #cyclic #mutable-borrow

nightly mutcy

Zero-cost safe mutable cyclic borrows using borrow relinquishing

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

Download history 823/week @ 2025-04-02 14/week @ 2025-04-09 304/week @ 2025-04-16 123/week @ 2025-04-23

1,264 downloads per month

MIT license

18KB
146 lines

Mutcy

Zero-cost mutable cyclic borrows using borrow relinquishing.

crates.io Documentation MIT licensed


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 KeyCells. 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.

  1. Borrowing is zero-cost.
  2. Borrowing will never fail or panic.
  3. 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].

No runtime deps