Funny I did the exact same thing when I went through the Box section of the 2nd ed Rust book. I was surprised that the simple "Cons" enum the book uses as an example is as tricky to work with as it is. I was also surprised that it required the use of mem::replace which the book hadn't mentioned yet and as far as I can tell, requires redundant copies to satisfy the compilers desire for references to be valid at all times.
enum List {
Link(i32, Box<List>),
Nil,
}
Here's the push_back/front (aka append/prepend) functions I came up with:
fn push_back(&mut self, val : i32) {
use std::mem;
match self {
List::Link(v, next) => {
// We need to create a boxed version of self so that self.next can point to it
// This is tricky because of our use of references. We can't move the current
// "next" out of self and into our new version without replacing it with
// something valid, so we put a List::Nil in it's place.
let old_next = mem::replace(next, Box::new(List::Nil));
let old_self = List::Link(*v, old_next);
// Update self to have the new val and point to the old list as "next"
mem::replace(next, Box::new(old_self));
*v = val;
},
List::Nil => {
*self = List::Link(val, Box::new(List::Nil));
}
}
}
To make the push_back work required switching to nightly and enabling NLL:
fn push_back(&mut self, val : i32) {
let mut x = self;
while let List::Link(_, next) = x {
x = next;
}
*x = List::Link(val, Box::new(List::Nil));
}
As a note, a nice extension to Rust's unsafe functionality would be to let you fiddle with references so as not to require mem::replace(). IMHO, the following is much cleaner and simple to understand than the original.
fn push_front_with_unsafe(&mut self, val : i32) {
match self {
List::Link(v, next) => {
unsafe references { // not a real rust feature
next = Box::new(List::Link(*v, next));
}
*v = val;
},
List::Nil => {
*self = List::Link(val, Box::new(List::Nil));
}
}
}
It turns out there's a cleaner, but still clunky IMHO, way to address the problem we needed mem::replace() for above. I got this from a postponed RFC https://github.com/ticki/rfcs/blob/replace_with/text/0000-mem-replace-with.md
pub fn replace_with<T, F>(mut_ref: &mut T, fn_replace: F)
where F: FnOnce(T) -> T {
use std::ptr;
unsafe {
let old_t = ptr::read(mut_ref);
let new_t = fn_replace(old_t);
ptr::write(mut_ref, new_t);
}
}
fn push_front2(&mut self, val : i32) {
match self {
List::Link(v, next) => {
replace_with(next, |orig_next| Box::new(List::Link(*v, orig_next)));
*v = val;
},
List::Nil => {
*self = List::Link(val, Box::new(List::Nil));
}
}
}