I'm trying to implement something like a zipper but taking advantage of mutable references to avoid having to deconstruct and reconstruct the data structure as I move through it. I've got example code for an attempt with a linked list, although I'd ideally like to apply it to other structures, like trees.
pub enum List<T> {
Empty,
Cons { head: T, tail: Box<List<T>> },
}
pub struct Zipper<'a, T: 'a> {
trail: Option<Box<Zipper<'a, T>>>,
focus: &'a mut List<T>,
}
impl<'a, T: 'a> Zipper<'a, T> {
pub fn down(&'a mut self) {
match self.focus {
&mut List::Empty => (),
&mut List::Cons {
tail: ref mut xs, ..
} => {
//We need a way to convince rust that we won't use oldZipper
//until xs goes out of scope
let oldZipper = std::mem::replace(
self,
Zipper {
trail: None,
focus: xs,
},
);
self.trail = Some(Box::new(oldZipper));
}
}
}
}
The borrow checker is not happy with this:
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:21:21
|
16 | tail: ref mut xs, ..
| ---------- first mutable borrow occurs here
...
21 | self,
| ^^^^ second mutable borrow occurs here
...
30 | }
| - first borrow ends here
This isn't surprising: if we have a zipper focused on a list and call down on it, we get zipper with a mutable reference to the tail of that list, so we have mutable aliasing.
However, if we never use the Zipper's trail before focus goes out of scope, we'll never be able to "see" the mutable aliasing. This seems analogous to normal mutable borrowing: you can't use the variable you borrowed from until the borrow goes out of scope.
Is there some way to explain this to the borrow checker? If you want to "explain" to the borrow checker that borrowing two non-overlapping slices from an array is okay, you can use split_at: is there some corresponding function that will enforce that trail is never used before focus goes out of scope, and in doing so, satisfies the borrow checker?
In order to achieve your goal, we need to get rid of the mutable reference in the
Zipperstruct. We can use mutable raw pointers instead: they let us mutate their referent, and we can more than one such pointer pointing at a particular object, but dereferencing them is unsafe.Here's the code:
The
focusfield in theZipperstruct is now a*mut List<T>. Because this is a raw pointer, we can copy it around freely. This resolves the compiler error you had inZipper::down. There's also a new field,_list, of typePhantomData<&'a mut List<T>>.PhantomDatais a special type that is meant to tell the compiler "pretend I'm storing/owning aT, even though I'm not". Without this field, the compiler would complain that the lifetime parameter'ais unused.Notice that
Zipper::newstill expects a&'a mut List<T>as a parameter: this allowsZipperto provide a safe interface by requiring the caller to have a unique mutable reference to theList<T>, a fact we can use to declare that the other unsafe operations in the struct are indeed safe since we have full knowledge of the available mutable references. As far as the compiler is concerned, aZipperis mutably borrowing theList; if you try to mutate aListwhile aZipperon theListis in scope, you'll get an error that theListis already mutably borrowed.You haven't shown any code that would let the user get a reference to the
Zipper's focus. I've been thinking of a possible implementation that would be unsafe, and it's tempting to go that route, but the compiler won't tell you it's wrong. Let me show you:It's tempting to return a
&'a mut List<T>because that's what we were given. However, it's wrong because the return value's lifetime is not bound toselfin any way, which means that we could callfocustwice to obtain two mutable references to the sameList<T>. If we still had a&'a mut List<T>inZipper, the compiler would tell us if we tried to return a&'a mut List<T>(unless we usedunsafecode to work around it). A correct implementation would be:In this implementation, the
Zipperwill be mutably borrowed as long as the returned&mut List<T>is around, which means we can't callfocus(ordown) until the&mut List<T>goes out of scope.