1 unstable release
new 0.1.1 | Feb 20, 2025 |
---|---|
0.1.0 |
|
#1390 in Rust patterns
23 downloads per month
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