I'm beginning with a Vec
of sorted nodes, then using this sorting to link these nodes together in a binary tree and then returning the base struct
// Test name
#[derive(Clone)]
struct Struct {
parent: Option<Rc<RefCell<Struct>>>,
superscript: Option<Rc<RefCell<Struct>>>,
subscript: Option<Rc<RefCell<Struct>>>,
height: u32,
center: u32,
symbols: VecDeque<u8>
}
Ending up with a binary tree formed by the above Struct
s. At this point, these Struct
s are uniquely owned, so I think I could convert from using Rc<RefCell<Struct>>
to RefCell<Struct>
(think Box<Struct>
doesn't work due to internal mutability?), but I'm not sure how or if this helps with the problem I'm encountering.
After this, I need to iterate in a novel manner through the Struct
s, and mutate the various symbols
belonging to the various Struct
s throughout the recursion, via calling .pop_front()
.
My current implementation doing this leads to various instances of thread 'main' panicked at 'already borrowed: BorrowMutError'
.
Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=636c93088f5a431d0d430d42283348f3
The function for it (please excuse the convoluted logic):
fn traverse_scripts(row: Rc<RefCell<Struct>>) {
if let Some(superscript_row) = &row.borrow().superscript {
if let Some(superscript_symbol) = superscript_row.borrow().symbols.front() {
if let Some(current_row_symbol) = row.borrow().symbols.front() {
if superscript_symbol < current_row_symbol {
println!("^{{{}",*superscript_symbol);
superscript_row.borrow_mut().symbols.pop_front();
traverse_scripts(Rc::clone(superscript_row));
}
}
else {
println!("^{{{}",*superscript_symbol);
superscript_row.borrow_mut().symbols.pop_front();
traverse_scripts(Rc::clone(superscript_row));
}
}
}
if let Some(subscript_row) = &row.borrow().subscript {
if let Some(subscript_symbol) = subscript_row.borrow().symbols.front() {
if let Some(current_row_symbol) = row.borrow().symbols.front() {
if subscript_symbol < current_row_symbol {
print!("_{{{}",*subscript_symbol);
subscript_row.borrow_mut().symbols.pop_front();
traverse_scripts(Rc::clone(subscript_row));
}
}
else {
print!("_{{{}",*subscript_symbol);
subscript_row.borrow_mut().symbols.pop_front();
traverse_scripts(Rc::clone(subscript_row));
}
}
}
if let Some(current_row_symbol) = row.borrow().symbols.front() {
if let Some(parent_row) = &row.borrow().parent {
if let Some(parent_symbol) = parent_row.borrow().symbols.front() {
if current_row_symbol < parent_symbol {
print!(" {}",*current_row_symbol);
row.borrow_mut().symbols.pop_front();
traverse_scripts(Rc::clone(&row));
}
}
}
else {
print!(" {}",*current_row_symbol);
row.borrow_mut().symbols.pop_front();
traverse_scripts(Rc::clone(&row));
}
}
if let Some(parent_row) = &row.borrow().parent {
if let Some(parent_symbol) = parent_row.borrow().symbols.front() {
print!("}} {}",*parent_symbol);
row.borrow_mut().symbols.pop_front();
traverse_scripts(Rc::clone(parent_row));
} else {
print!("}}");
traverse_scripts(Rc::clone(parent_row));
}
}
}
I've considered using Arc<Mutex<Struct>>
instead for the traversal, but given its not multi-threaded I don't think it's neccessary?
I imagine I might be missing a relatively simple idea here, I would really appreciate any help.
If I've missed anything in my question please drop a comment and I'll try to add it.
When you call borrow
or borrow_mut
on a RefCell
, a guard object (Ref
or RefMut
) is created that grants access to the inner value for as long as it exists. This guard will lock the RefCell
until it goes out of scope and is destroyed. Let's look at a portion of traverse_scripts
:
if let Some(superscript_row) = &row.borrow().superscript { // row is borrowed
if let Some(superscript_symbol) = superscript_row.borrow().symbols.front() { // superscript_row is borrowed
if let Some(current_row_symbol) = row.borrow().symbols.front() { // row is borrowed again
if superscript_symbol < current_row_symbol {
println!("^{{{}", *superscript_symbol);
superscript_row.borrow_mut().symbols.pop_front(); // superscript_row is borrowed mutably (ERROR)
traverse_scripts(Rc::clone(superscript_row)); // recursive call while row and superscript_row are borrowed (ERROR)
}
} else {
println!("^{{{}", *superscript_symbol);
superscript_row.borrow_mut().symbols.pop_front(); // superscript_row is borrowed mutably (ERROR)
traverse_scripts(Rc::clone(superscript_row)); // recursive call while row and superscript_row are borrowed (ERROR)
} // row is no longer borrowed twice
} // superscript_row is no longer borrowed
} // row is no longer borrowed
In the first line, for example, row.borrow()
returns a Ref<Struct>
. This Ref
can't be dropped immediately, because it's being borrowed during the if let
body by superscript_row
. So it stays alive that whole time -- until the final }
.
This is a problem for recursively calling traverse_scripts
because the Struct
is borrowed during the entire recursive call. Any attempt to borrow the same Struct
mutably deeper in the call stack will fail. (Borrowing it immutably does still work.)
In the second line superscript_row
is borrowed. This has the same problem, but it also has a more immediate one: it's borrowed mutably later in the same function, even before hitting the recursive call. That borrow_mut
can never succeed because superscript_row
is always already borrowed at that point.
To fix both problems, we'll do two things:
Ref
or RefMut
guard in a variable of its own and re-use that guard, instead of calling borrow()
or borrow_mut()
on the same variable again.drop
each of the still-living guards so that nothing is still borrowed inside the recursive call.Here's what that section might look like rewritten:
{ // This scope will constrain the lifetime of row_ref
let row_ref = row.borrow();
if let Some(superscript_row) = &row_ref.superscript {
let mut child = superscript_row.borrow_mut(); // use borrow_mut here because we know we'll need it later
if let Some(superscript_symbol) = child.symbols.front() {
if let Some(current_row_symbol) = row_ref.symbols.front() {
if superscript_symbol < current_row_symbol {
println!("^{{{}", *superscript_symbol);
child.symbols.pop_front();
drop(child); // child is no longer needed, so drop it before recursing
// Since superscript_row borrows from row_ref, we must Rc::clone it before
// dropping row_ref so that we can still pass it to traverse_scripts.
let superscript_row = Rc::clone(superscript_row);
drop(row_ref); // row_ref is no longer needed, so drop it before recursing
traverse_scripts(superscript_row);
}
} else {
println!("^{{{}", *superscript_symbol);
child.symbols.pop_front();
// see comments earlier
drop(child);
let superscript_row = Rc::clone(superscript_row);
drop(row_ref);
traverse_scripts(superscript_row);
}
}
} // child is dropped here (if it wasn't already). superscript_row is no longer borrowed
} // row_ref is dropped here (if it wasn't already). row is no longer borrowed
This looks complicated because it is complicated. Traversing over a data structure while mutating it is a common source of bugs (in most languages, not just Rust). It looks like, in traverse_scripts
at least, the only reason for needing mutation is to call pop_front
on symbols
, so if you can redesign the data structure such that only symbols
is in a RefCell
, you could do the traversal with only &
references, which would be much easier. Another common approach is to write functions that return new data structures instead of mutating them in place.