Search code examples
rust

Different behaviour for same match and if let blocks


I'm trying to figure out how I'm offending the rust compiler but I'm not able to wrap my head around whats wrong.

So this is the basic algorithm:

  1. Get the mutable reference to the list.
  2. If the next node is null, allocate and assign to next node pointer; break
  3. If the next node is not null, get a mutable reference to next node; goto 1

And this is how I coded it up

fn append_at_end(&mut self, value: i32) {
    match self {
        List::Empty => {
            *self = List::new_node(value);
        },
        List::Head(head) => {
            let mut itr = head;
            loop {
                if let Some(x) = itr {
                    if x.next.is_none() {
                        x.next = Some(Node::new_node(value));
                        break;
                    } else {
                        itr = &mut x.next;
                    }
                }
            }
        }
    }
}

Now the rust compiler complains for this block of code

   Compiling random v0.1.0 (/Users/katharva/Personal/rust/random)
error[E0503]: cannot use `*itr` because it was mutably borrowed
  --> src/main.rs:65:28
   |
65 |                     if let Some(x) = itr {
   |                            ^^^^^-^
   |                            |    |
   |                            |    `itr.0` is borrowed here
   |                            use of borrowed `itr.0`
   |                            borrow later used here

error[E0499]: cannot borrow `itr.0` as mutable more than once at a time
  --> src/main.rs:65:33
   |
65 |                     if let Some(x) = itr {
   |                                 ^    --- first borrow used here, in later iteration of loop
   |                                 |
   |                                 `itr.0` was mutably borrowed here in the previous iteration of the loop

Some errors have detailed explanations: E0499, E0503.
For more information about an error, try `rustc --explain E0499`.
error: could not compile `random` (bin "random") due to 2 previous errors

But the same code written as

fn append_at_end_option2(&mut self, value: i32) {
    match self {
        List::Empty => {
            *self = List::new_node(value);
        },
        List::Head(head) => {
            let mut itr = head;
            loop {
                match itr {
                    None => {}, 
                    Some(x) => {
                        // A bit convoluted but shows the bug
                        if x.next.is_none() {
                            x.next = Some(Node::new_node(value));
                            break;
                        } else {
                            itr = &mut x.next;
                        }
                    }
                }
            }
        }
    }
}

Or

fn append_at_end_option1(&mut self, value: i32) {
    match self {
        List::Empty => {
            *self = List::new_node(value);
        },
        List::Head(head) => {
            let mut itr = head;
            loop {
                if let Some(x) = itr {
                    itr = &mut x.next;
                    continue;
                } 
                
                *itr = Some(Node::new_node(value));
                break;
            }
        }
    }
}

is okay, the compiler is happy with it. Both append_at_end_option2 and append_at_end_option1 will work as expected. As per my understanding all 3 pieces of the code have the same logic. So why does the first one not acceptable?


Solution

  • Apparently this works in the rust playground. Seems like a bug with the compiler which was fixed.

    https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fc48a275e5f34536658727c9a6b097af