#cell #container #borrow #reference #refcell #dynamic #mut

no-std flexcell

A dynamic borrow container allowing temporary replacement of internal references

1 unstable release

new 0.1.1 Feb 20, 2025
0.1.0 Feb 20, 2025

#1390 in Rust patterns

23 downloads per month

MIT/Apache

29KB
419 lines

FlexCell

FlexCell is a container that helps you manage dynamic (runtime) borrowing of a value, in scenarios where standard Rust borrow rules or RefCell alone might give you "double borrow" errors. It's something like a RefCell<Option<Box<T>>> but with an additional ability to "lend" external references into the cell without having to refactor all of your function signatures or logic.

Motivation

Consider a situation with a struct that holds a RefCell<Window>, and you also have a function that receives a &mut Window separately:

struct App {
    window: Rc<RefCell<Window>>,
}

impl App {
    fn feed(&mut self, event: Event) {
        let window = self.window.borrow_mut();
        /* ... */
    }
}

fn handle_event(event: Event, app: &mut App, window: &mut Window) {
    // do something with the window
    window.do_something();

    // now app.feed(event) might also need to borrow window internally,
    // which can lead to a double-borrow error if it tries to use the same RefCell<Window>.
    app.feed(event);
}

If app.feed(event) ends up trying to borrow app.window, that conflicts with the &mut Window already passed in and can cause a runtime RefCell borrow error. One workaround is to change the function signatures or pass window around differently, but that might not be feasible if it comes from a third-party library or if it's widely used in your codebase.

FlexCell solves this problem by letting you "lend" the external &mut Window to the FlexCell temporarily:

use flexcell::FlexCell;

struct App {
    window: Rc<FlexCell<Window>>,
}

impl App {
    fn feed(&mut self, event: Event) {
        self.window.borrow_mut(|window: &mut Window| {
            /* ... */
        });
    }
}

fn handle_event(event: Event, app: &mut App, window: &mut Window) {
    // do something with the window
    window.do_something();

    // now app.feed(event) might also need to borrow the same window internally,
    // which would normally lead to a double-borrow error if using the same RefCell<Window>.
    // Instead, we "lend" this &mut Window to the FlexCell, so it temporarily uses our
    // external reference without causing conflicts.
    app.window.clone().lend(window, || {
        app.feed(event);
    });
}

Under the hood, FlexCell temporarily replaces its own internal pointer with window so when app.feed borrows from app.window, it's actually borrowing from the window you supplied. After the closure finishes, FlexCell swaps its pointer back to the original owned data.

This approach lets you avoid code refactoring purely to fix borrow collisions. In short:

  • You can keep using your existing function signatures.
  • You avoid "double borrow" problems.

Basic Example

use flexcell::FlexCell;

fn main() {
    // Create a FlexCell that owns the value 100.
    let cell = FlexCell::new(100);

    // Borrow it immutably in a closure.
    cell.borrow(|value| {
        println!("Current value is {}", value);
    });

    // Borrow it mutably to modify the value.
    cell.borrow_mut(|value| {
        *value += 10;
    });

    // Lend an external value temporarily.
    let mut external = 42;
    cell.lend(&mut external, || {
        // During this closure, the cell pointer is replaced with `&mut external`.
        cell.borrow_mut(|value: &mut i32| {
            assert_eq!(value, &mut 42);
            *value += 10;
        });
        // Cell cannot be borrowed or taken for anything else while this "lend" is active.
    });

    // External value is updated.
    assert_eq!(external, 52);

    // Finally, remove the internal value fully.
    let taken = cell.take();
    assert_eq!(taken, 110);
}

License

FlexCell is made available under either the MIT or Apache-2.0 license.

Dependencies

~225–670KB
~15K SLoC