I’m trying to model a typestate API where a mutable borrow of a parent object is temporarily narrowed into a mutable borrow of one child field, held across multiple method calls, then consumed to recover the parent state.
Simplified example:
struct Table {
columns: Vec,
}
struct Column {
data: String,
offsets: Vec,
}
struct RowBuilder<'a> {
table: &'a mut Table,
current_col: usize,
}
struct FieldBuilder<'a> {
table: &'a mut Table,
data: &'a mut String, // borrowed from table.columns[current_col].data
current_col: usize,
}
Desired API:
let mut row = table.begin_row();
let mut field = row.begin_field();
field.push_str("hello");
field.push_str(" world");
row = field.finish();
let mut field = row.begin_field();
field.push_str("next");
row = field.finish();
row.commit();
Conceptually this is linear:
&mut Table
-> RowBuilder
-> FieldBuilder borrowing one column's String
-> RowBuilder
-> commit
Only one state exists at a time. begin_field consumes RowBuilder; finish consumes FieldBuilder and returns RowBuilder.
But I don’t see how to represent FieldBuilder safely, because it wants both:
table: &'a mut Table
data: &'a mut String // sub-borrow from inside table
Questions:
- Is there a safe idiomatic way to express this parent borrow → child borrow → parent borrow transformation?
- Can this be done with borrow splitting / split_at_mut, or does the long-lived child borrow require a self-referential pattern?
- Are crates like ouroboros or self_cell appropriate here?
- Is the usual solution to hide a raw pointer internally and expose a safe linear typestate API?
Minimal sketch of intended methods:
impl<'a> RowBuilder<'a> {
fn begin_field(self) -> FieldBuilder<'a> {
todo!()
}
}
impl<'a> FieldBuilder<'a> {
fn push_str(&mut self, s: &str) {
self.data.push_str(s);
}
fn finish(self) -> RowBuilder<'a> {
// write offset, advance current_col, recover row builder
todo!()
}
}