Borrow checker complains for closure inside loop if type not provided explicitly
Asked Answered
T

2

15

I have this code, for which borrow checker shows error:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=55d050b6f25410ce0e17ef9e844b048d

fn f1(v: &str) {
}

fn main() {
    let c = |v| f1(v);
    for _ in 0..1 {
        let s = String::new();
        c(&s);
    }
}
   |
10 |         c(&s);
   |         - ^^ borrowed value does not live long enough
   |         |
   |         borrow later used here
11 |     }
   |     - `s` dropped here while still borrowed

But if I add explicit type to closure, code compiles let c = |v: &str| f1(v);

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=54ca20dff5bdf1831ec705481e4936bb

Can someone explain why it works in second case and not in first, as I understand rust correctly infers same type in first example (as it works if I run closure outside of loop)?

Titfer answered 12/9, 2020 at 1:31 Comment(2)
Seems like a compiler bug. Opened an issue for the same.Erhart
One bug plus another one equals a solution. SCNR.Kele
G
1

This is because the result of closure c outlives the loop. For example, in the loop, you pass &s to c but at the end of the loop c still exist. This is why the in-place construction of closure works. Even the following code works alright.

fn f1(v: &str) {}

fn main() {
    for _ in 0..1 {
        let s = String::new();
        let c = |v| f1(v);
        c(&s);
    }
}

In essence, the borrowed reference cannot be held by someone when you know the reference cease to exist before the borrower.

Also note the following approaches work too;

fn f1(v: &str) {}

fn main() {
    let c = f1;

    for _ in 0..1 {
        let s = String::new();
        c(&s);
    }
}

and

fn f1(v: &str) {}

fn c(v: &str) {}

fn main() {
    for _ in 0..1 {
        let s = String::new();
        c(&s);
    }
}

Because in both of these cases, your borrowed reference does not outlive the scopes. But in your closure syntax v the input goes to f(v) which is held by c.

In a way you can think that c is a variable that would hold onto the reference which dies before c does.

Your example is interesting and might even be an edge case. In case you were wondering closures capture the surrounding unlike variables and functions. This is discussed here further rust book.

Because the following code compiles.

fn main() {
    let mut c;

    for _ in 0..1 {
        let s = String::new();
        c = &s ;
    }
}

But this fails

fn main() {
    let mut c;

    for _ in 0..1 {
        let s = String::new();
        c = &s ;
    }

    println!("{c}");
}

Because compiler knows in previous scenario c can be cleared right at the end of for loop, but the second one is a clear no, due to outlived reference.

I hope this shed some light on your question.

Goatherd answered 5/7 at 5:24 Comment(0)
T
0

&str is not same &String

borrowed value does not live long enough

&str have a static lifetime which means is guaranteed to be valid for the duration of the entire program.

But ref String(&s) only lives inside the loop iteration, and then it dies. A variable only lives as long as its code block.

For example, without loop, it's compile normally:

fn main() {                                                                                                                                                                                                                           
     let c = |v| f1(v);                                                                                                                                                                                      
     let s = String::new();                                                                                                                                                                                              
     c(&s);                                                                                                                                                                                        
}

If you want, you can pass the ownership of string variable to c function, its make your string live after iteration.

fn f1(v: &str) {
}

fn main() {
    let c = |v:String| f1(&v);
    for _ in 0..1 {
        let s = String::new();
        c(s);
    }
}
Tighe answered 5/10, 2023 at 21:24 Comment(4)
This... does not answer the question.Kalgan
Please, let me know why.... because the compilers not speak about wrong infers... this speak about "not live long enough"....Tighe
this is a correct explanation.Civilly
This is missing some key information, even if it is correct. For one thing, how what it the implied type of c in the first case? For another, your second (non-loop) example is using pass-by-value instead of borrowing, unlike the first case.Revenant

© 2022 - 2024 — McMap. All rights reserved.