I have a program that uses a QuadTree. This tree stores mutable borrows to data that is owned by another container (a Vec). I rebuild the QuadTree every game loop, but I do not want to reallocate, so I clear the underlying Vecs of the QuadTree instead of reconstructing it from scratch.
A simplified example that demonstrates the same problem is shown below. Instead of a QuadTree, here I am just using another Vec as this has identical issues.
struct A; fn main() { let mut owned_data = vec![A, A, A]; let mut mut_borrowed_data = vec![]; '_outer: loop { mut_borrowed_data.clear(); '_inner: for borrow in &mut owned_data { mut_borrowed_data.push(borrow); } } } This gives the error:
error[E0499]: cannot borrow `owned_data` as mutable more than once at a time --> src\main.rs:8:30 | 8 | '_inner: for borrow in &mut owned_data { | ^^^^^^^^^^^^^^^ `owned_data` was mutably borrowed here in the previous iteration of the loop The issue isn't really that I am mutably borrowing in a previous iteration of the outer loop. If I remove the mut_borrowed_data.push(data); it compiles, because the borrow checker realises that the mutable borrow of owned_data is dropped at the end of each outer loop, therefore the number of mutable borrows is a max of 1. By pushing into mut_borrowed_data, this mutable borrow is moved into this container (Please correct me if I am wrong here), therefore it isn't dropped and the borrow checker is not happy. If I did not have the clear there would be multiple copies of the mutable borrow, and the borrow checker is not smart enough to realise that I only push into the mut_borrowed_data once, and that I clear it every outer loop.
But as it stands, there is only one instance of the mutable borrow at any one time, so is the following code safe/sound?
struct A; fn main() { let mut owned_data = vec![A, A, A]; let mut mut_borrowed_data = vec![]; '_outer: loop { mut_borrowed_data.clear(); '_inner: for borrow in &mut owned_data { let ptr = borrow as *mut A; let new_borrow = unsafe { &mut *ptr }; mut_borrowed_data.push(new_borrow); } } } This now compiles. The mutable borrow of owned_data (named borrow) is not moved into the mut_borrowed_data and therefore it is dropped at the end of the outer loop. This means owned_data is only mutable borrowed once. The unsafe code takes a copy of the pointer to the data, dereferences it and creates a new borrow to that. (again, please correct me if I am wrong). Because this uses a copy and not a move, the compiler allows borrow and new_borrow to exist at the same time. This use of unsafe could break the borrow rules, but as long as I do not use borrow after I have created new_borrow, and as long as I clear mut_borrowed_data, then I think this is safe/sound.
Moreover, (I think) the guarantees given by the borrow checker still hold as long as I clear the mut_borrowed_data vec. It won't let me push into mut_borrowed_data twice in one loop, because the new_borrow is moved after it is first inserted.
I do not want to use a RefCell as I want this to be as performant as possible. The whole purpose of the QuadTree is to increase performance so I want to make any overhead it introduces as lean as possible. Incrementing the borrow count is probably cheap, but the branch (to check if that value is <= 1), the indirection, and the decreased simplicity of my data, are too much for me to feel happy about.
Is my use of unsafe here safe/sound? Is there anything that could trip me up?
slice::split_at_mutdoes essentially the same thing, using unsafe to guarantee that two &muts obtained from the same container do not overlap.