fieldwork-borrow

Crates.iofieldwork-borrow
lib.rsfieldwork-borrow
version0.1.0
created_at2025-12-08 13:00:10.406601+00
updated_at2025-12-08 13:00:10.406601+00
descriptionField-level borrow splitting for Rust
homepage
repositoryhttps://github.com/Erio-Harrison/fieldwork-borrow
max_upload_size
id1973482
size47,713
Haoren(Harrison) Hu (Erio-Harrison)

documentation

README

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) {
        // ERROR: cannot borrow `self.object_mats` as mutable because
        // `self` is also borrowed as immutable (by cur_model)
        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:

  1. Extracts fields into local variables with precise borrowing
  2. Inlines method bodies at call sites
  3. Transforms self.field accesses to use the local variables

Syntax

with_!(self, {
    methods {
        method_name(field1, mut field2, copy field3, extra_arg: Type) -> ReturnType {
            // method body - field1/field2/field3 are available here
        }
    }
    fields {
        field_a           // immutable borrow (&self.field_a)
        mut field_b       // mutable borrow (&mut self.field_b)
    }
    body {
        // your code here
        // - self.method_name(...) calls are inlined
        // - self.field_a / self.field_b access the local variables
        // - field_a / field_b also work directly
    }
});

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

Commit count: 0

cargo fmt