Why is this a borrow error? Type annotations fix it

On GitHub with context

This errors:

let mut process = |line| {
    unimplemented!();
};
let mut line = resolver.first_line_of(&slice.span);
process(&line);
while let Some(next) = resolver.next_line_of(&slice.span, line) {
    line = next;
    process(&line);
}
error[E0505]: cannot move out of `line` because it is borrowed
  --> crates\retort\src\render\list.rs:65:79
   |
64 |                     process(&line);
   |                             ----- borrow of `line` occurs here
65 |                     while let Some(next) = resolver.next_line_of(&slice.span, line) {
   |                                                                               ^^^^ move out of `line` occurs here
66 |                         line = next;
67 |                         process(&line);
   |                         ------- borrow later used here

error[E0506]: cannot assign to `line` because it is borrowed
  --> crates\retort\src\render\list.rs:66:25
   |
64 |                     process(&line);
   |                             ----- borrow of `line` occurs here
65 |                     while let Some(next) = resolver.next_line_of(&slice.span, line) {
66 |                         line = next;
   |                         ^^^^ assignment to borrowed `line` occurs here
67 |                         process(&line);
   |                         ------- borrow later used here

If I annotate the input type of the closure, it works just fine:

let mut process = |line: &SpannedLine<Sp>| {
    unimplemented!();
};
let mut line = resolver.first_line_of(&slice.span);
process(&line);
while let Some(next) = resolver.next_line_of(&slice.span, line) {
    line = next;
    process(&line);
}

Reordering the process and assignment makes it emit more errors:

let mut process = |line| {
    unimplemented!();
};
let mut line = resolver.first_line_of(&slice.span);
process(&line);
while let Some(next) = resolver.next_line_of(&slice.span, line) {
    process(&next);
    line = next;
}
error[E0505]: cannot move out of `line` because it is borrowed
  --> crates\retort\src\render\list.rs:65:79
   |
64 |                     process(&line);
   |                             ----- borrow of `line` occurs here
65 |                     while let Some(next) = resolver.next_line_of(&slice.span, line) {
   |                                                                               ^^^^ move out of `line` occurs here
66 |                         process(&next);
   |                         ------- borrow later used here

error[E0597]: `next` does not live long enough
  --> crates\retort\src\render\list.rs:66:33
   |
66 |                         process(&next);
   |                         ------- ^^^^^ borrowed value does not live long enough
   |                         |
   |                         borrow later used here
67 |                         line = next;
68 |                     }
   |                     - `next` dropped here while still borrowed

error[E0506]: cannot assign to `line` because it is borrowed
  --> crates\retort\src\render\list.rs:67:25
   |
64 |                     process(&line);
   |                             ----- borrow of `line` occurs here
65 |                     while let Some(next) = resolver.next_line_of(&slice.span, line) {
66 |                         process(&next);
   |                         ------- borrow later used here
67 |                         line = next;
   |                         ^^^^ assignment to borrowed `line` occurs here

error[E0505]: cannot move out of `next` because it is borrowed
  --> crates\retort\src\render\list.rs:67:32
   |
66 |                         process(&next);
   |                         ------- ----- borrow of `next` occurs here
   |                         |
   |                         borrow later used here
67 |                         line = next;
   |                                ^^^^ move out of `next` occurs here

Involved signatures:

resolver: &mut dyn SpanResolver<Sp>
SpanResolver<Sp>.first_line_of(&mut self, span: &Sp) -> SpannedLine<Sp>
SpanResolver<Sp>.next_line_of(&mut self, span: &Sp, previous: SpannedLine<Sp>) -> Option<SpannedLine<Sp>>

Rust seems to be assuming that process is somehow capturing the lifetime of the &line given to it, but realizes that it doesn't when I give it an explicit input type annotation.

This looks like a case where the compiler is choosing move semantics for the closure. I cannot answer why, but type inference wants to move line into the closure.

While playing with this in the playground, I found that another workaround is this weird boxed closure to annotate the argument as a borrowed type:

    let mut process: Box<dyn Fn (&_)> = Box::new(|line| {
        unimplemented!();
    });

Reduced:

fn main() {
    let process = |_| {};
    let mut line = 1i32;
    process(&line);
    line = 2i32;
    process(&line);
}
1 Like