fieldwork-borrow
Field-level borrow splitting for Rust.
Rust's borrow checker operates at the function boundary level, which sometimes rejects code that is actually safe. fieldwork-borrow provides a with_! macro that enables field-level borrow splitting, allowing you to work around these "false positive" borrow checker errors.
The Problem
Consider this common pattern that Rust rejects:
struct Viewer {
db: Database,
model_id: usize,
object_mats: Vec<f64>,
}
impl Viewer {
fn cur_model(&self) -> &Model {
&self.db.models[self.model_id]
}
fn problem(&mut self) {
for obj in &self.cur_model().objects {
self.object_mats.push(obj.matrix);
}
}
}
The code is safe because cur_model() only reads db and model_id, while we're mutating object_mats. But Rust can't see through the method boundary.
The Solution
use fieldwork_borrow::with_;
impl Viewer {
fn problem(&mut self) {
with_!(self, {
methods {
cur_model(&db, copy model_id) -> &Model {
&db.models[model_id]
}
}
fields {
mut object_mats
}
body {
for obj in &self.cur_model().objects {
object_mats.push(obj.matrix);
}
}
});
}
}
The with_! macro:
- Extracts fields into local variables with precise borrowing
- Inlines method bodies at call sites
- Transforms
self.field accesses to use the local variables
Syntax
with_!(self, {
methods {
method_name(field1, mut field2, copy field3, extra_arg: Type) -> ReturnType {
}
}
fields {
field_a mut field_b }
body {
}
});
Field Access Modes
In method signatures:
field or &field - immutable borrow
mut field - mutable borrow
copy field - copy by value (for Copy types)
In fields block:
field - immutable borrow
mut field - mutable borrow
Examples
Closure with get_or_insert_with
fn problem(&mut self) -> &mut SomeState {
with_!(self, {
methods {
assign_next_page(mut page) -> u32 {
let p = *page;
*page += 1;
p
}
}
fields { mut state }
body {
state.get_or_insert_with(|| {
let x = self.assign_next_page();
new_state(x)
})
}
})
}
Iterator with method call
fn problem(&mut self) {
with_!(self, {
methods {
append_slice(mut slices, val: u64, idx: usize) {
slices.some_code();
}
}
fields { segments }
body {
for (i, s) in segments.iter().enumerate() {
self.append_slice(some_f(s), i);
}
}
});
}
Multiple methods accessing shared state
fn problem(&mut self) {
with_!(self, {
methods {
cur_model(&db, copy model_id) -> &Model {
&db.models[model_id]
}
cur_animation(&db, &conn, copy model_id) -> Option<&Animation> {
conn.models[model_id].animations.get(0)
.map(|&id| &db.animations[id])
}
}
fields { mut uv_mats, mat_anim_state }
body {
if let Some(anim) = self.cur_animation() {
for (i, _) in self.cur_model().materials.iter().enumerate() {
uv_mats[i] = anim.tracks[0].eval(mat_anim_state.frame);
}
}
}
});
}
Limitations
- Methods defined in
with_! are inlined at call sites, so they cannot be recursive
- For recursive patterns, extract the recursive logic into a standalone function that takes the split fields as parameters
- The
? operator in method bodies may need adjustment since the method is inlined
Installation
[dependencies]
fieldwork-borrow = "0.1"
License
MIT