Here's a minimal example:
use crate::List::{Cons, Nil};
enum List {
Cons(i32, Box<List>),
Nil,
}
struct Tail<'a> {
tail: &'a mut List,
}
impl<'a> Tail<'a> {
fn tail(list: &'a mut List) -> Self {
let mut next = list;
loop {
match next {
Cons(_, t) => match t as &List {
Cons(_, _) => next = t,
Nil => break,
},
Nil => break,
}
}
Self { tail: next }
}
}
fn main() {
let mut bar = Cons(20, Box::from(Nil));
let _tail = Tail::tail(&mut bar);
}
I'm getting
error[E0499]: cannot borrow `*next` as mutable more than once at a time
--> src/main.rs:24:22
|
12 | impl<'a> Tail<'a> {
| -- lifetime `'a` defined here
...
17 | Cons(_, t) => match t as &List {
| - first mutable borrow occurs here
...
24 | Self { tail: next }
| -------------^^^^--
| | |
| | second mutable borrow occurs here
| returning this value requires that `next.1` is borrowed for `'a`
I'm misunderstanding something and thinking that t
(the first mutable borrow) in the Cons(_, t)
match arm should go out of scope by the time tail: next
(the second mutable borrow) happens.
Maybe it's important to note that I don't think the next = t
assignment is a move but rather sugar for something like next = t.deref_mut()
. But still, why does the Rust compiler think I have two mutable borrows in the same scope? Is there any way to tell the compiler to drop t
after the match block it's lexically scoped to?
This version where I tried to lookahead a bit faster in the same iteration before destructuring into mutable borrows works, but oh man, it sure looks like there's gotta be a better way doesn't it?
use crate::List::{Cons, Nil};
enum List {
Cons(i32, Box<List>),
Nil,
}
#[allow(dead_code)]
struct Tail<'a> {
tail: &'a mut List,
}
impl<'a> Tail<'a> {
fn tail(list: &'a mut List) -> Self {
let mut next = list;
loop {
match next {
Cons(_, t) => match t as &List {
Cons(_, _) => {
next = t;
match next {
Nil => return Self { tail: next },
_ => continue,
}
}
_ => panic!(),
},
Nil => return Self { tail: next },
}
}
}
}
fn main() {
let mut bar = Cons(20, Box::from(Nil));
let _tail = Tail::tail(&mut bar);
}
Wait, no, the compiler actually caused me to mess up the runtime logic. Ok, refactored to do an O(2N) operation b.c. there's no way to do this in one shot AFAIK:
use crate::List::{Cons, Nil};
use std::fmt::Debug;
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
#[derive(Debug)]
struct Tail<'a> {
tail: &'a mut List,
}
impl<'a> Tail<'a> {
fn _get_tail_pos(list: &List) -> usize {
let mut next = list;
let mut idx: usize = 0;
loop {
match next {
Cons(_, t) => {
idx += 1;
next = t;
}
Nil => break,
}
}
idx
}
fn tail(list: &'a mut List) -> Self {
let tail_pos = Self::_get_tail_pos(list);
let mut next = list;
if tail_pos == 0 || tail_pos == 1 {
Self { tail: next }
} else {
let mut i: usize = 0;
loop {
match next {
Cons(_, t) => {
next = t;
i += 1;
if i == tail_pos - 1 {
return Self { tail: next };
}
}
Nil => panic!()
}
}
}
}
}
fn main() {
let mut bar = Nil;
let tail = Tail::tail(&mut bar);
println!("{:?}", tail);
let mut bar = Cons(20, Box::from(Nil));
let tail = Tail::tail(&mut bar);
println!("{:?}", tail);
let mut bar = Cons(20, Box::from(Cons(30, Box::from(Nil))));
let tail = Tail::tail(&mut bar);
println!("{:?}", tail);
}
The above outputs:
Tail { tail: Nil }
Tail { tail: Cons(20, Nil) }
Tail { tail: Cons(30, Nil) }
Which is the intended behavior. TL;DR, I ended up searching for the index position first, and then I tried to recurse through the data structure to find where I want to place my &mut
. Maybe that's how the Rust compiler wanted me to design this.