1

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.

solstice333
  • 3,399
  • 1
  • 31
  • 28
  • 2
    This looks very similar to: [Why does conditional assignment of a matched mutable reference cause borrow errors?](https://stackoverflow.com/q/64639030/2189130) The borrow checker doesn't handle this case very well at the moment. – kmdreko Dec 22 '20 at 10:09
  • also, I wonder if `Self { tail: next }` should be a move of `next` into `tail`... – solstice333 Dec 22 '20 at 22:16

0 Answers0