Code fails to compile after explicitly specifying the type of the closure's parameter

this code can be compiled successfully:

fn main() {
    let mut words: Vec<&str> = vec![];
    let mut push_str = |s| { words.push(s) };

    push_str("hello");
    push_str("world");

    assert_eq!(words.len(), 2);
}

however, it fails when i specify the type of the parameter 's' of the closure's

fn main() {
    let mut words: Vec<&str> = vec![];
    let mut push_str = |s:&str| { words.push(s) };

    push_str("hello");
    push_str("world");

    assert_eq!(words.len(), 2);
}

The output error is like that:

error[E0521]: borrowed data escapes outside of closure
 --> src/main.rs:3:35
  |
2 |     let mut words: Vec<&str> = vec![];
  |         --------- `words` declared here, outside of the closure body
3 |     let mut push_str = |s:&str| { words.push(s) };
  |                         -         ^^^^^^^^^^^^^ `s` escapes the closure body here
  |                         |
  |                         `s` is a reference that is only valid in the closure body

Can someone answer my confusion? thanks!

I try it.
But I found this code has error:

fn push_str_fn() -> impl FnMut(&mut Vec<&str>, &str) {
	|words, s| { words.push(s) }
}

The output error is like this:

error: lifetime may not live long enough
  --> src/main.rs:26:16
   |
26 |         |words, s| { words.push(s) }
   |          -----  -    ^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2`
   |          |      |
   |          |      has type `&'1 str`
   |          has type `&mut Vec<&'2 str>`

So, I think it is about lifetime. And the compiler don't check when we don't specify the type of the parameter 's' of the closure's.
If checking, the compiler will find a lifetime problem.

Right Code:

fn main2() {
    let mut words: Vec<&str> = vec![];
    let mut push_str = |s: &'static str| { words.push(s) };

    push_str("hello");
    push_str("world");

    assert_eq!(words.len(), 2);
}

We need to use lifetime 'static.
If not, may the &str will drop in other code because it is from copy an other &str and the other can drop anytime.

'static is not suitable for this:

fn main() {
    let mut words: Vec<&str> = vec![];
    let mut push_str = |s| { words.push(s) };

    push_str("hello");
    push_str("world");

    let info = "dont-care".to_string();
    push_str(&info);

    assert_eq!(words.len(), 3);
}

The problem is very obvious.
The type of info is String. So It will drop at the end of the scope. It means, info do not have lifetime 'static.

Defining the types of closures is tricky, because there's some features of a function signature that cannot be directly expressed in closure syntax (unlike regular fn syntax).

When you explicitly specify a reference type without a lifetime for a closure parameter, that is, |s: &str|, the compiler takes the type as being like a regular function parameter, fn push_str(s: &str) — which without lifetime elision is fn push_str<'a>(s: &'a str) (or as a function trait bound, for<'a> FnMut(&'a str)), which means that the function accepts any lifetime. But words doesn't accept “any lifetime”, so it won't compile.

When you don't specify the type, the elided <'a> lifetime generic isn't created, and the type is determined by regular type inference, which lets the type be &'same_lifetime_as_in_the_vec str which is what your code needs.

So, in this case, you have to not specify the type. Or, you can use the trick to fully define the type, which is to pass the closure to another function which has the function trait bound you want the closure to implement:

fn constrain<'a, F: FnMut(&'a str)>(f: F) -> F {
    f
}

...
    let mut push_str = constrain(|s: &str| words.push(s));

This works because of a special pragmatic rule in the compiler about closure types: if the closure is immediately (not through a variable) passed to some place that constrains its type, then the closure type is made to fit it. In Rust, closures are almost always immediately passed to functions, so this rule helps out a lot.

Another case is that if the lifetime you need has a name, you can just use that name:

fn pusher<'a>(words: &mut Vec<&'a str>, s: &'a str) {
    let mut push_str = |s: &'a str| words.push(s);

    push_str(s);
    push_str(s);
}
9 Likes

No, this is absolutely not the case. Types, lifetimes, trait bounds, etc. are always checked.

When you leave off the parameter type of a closure, it will be inferred, which can (and usually does) result in the correct type. You can override it manually, which can (and in your case, does) result in the incorrect type.

But there is no situation in which the compiler would silently ignore lifetimes or any other aspect of the type system.

2 Likes

In your example above that, there is no for<'a>, did you mean to show one?

Whoops, I edited halfway and ended up inconsistent. I've added both syntaxes now.

2 Likes

Thanks. Your post is very clear, in fact I've bookmarked it.

1 Like

what 'a means? it only works on the &str instead of the F ?
but it can works as expected ~~ This is causing me so much pain

I can understand the following writing style:

fn constrain<'a, 'b:'a, F: FnMut(&'a str) + 'b>(f: F) -> F {
    f
}

'a is a lifetime. When used as a parameter to a generic type such as a &'a T reference, it means that the borrow lasts for 'a, while as a bound on a type (T: 'a), it means that instances of the type are valid for at least 'a (meaning that a borrow &'a T can be created by taking the address of such an instance).

1 Like

All the code in the rest of my reply is available for playing with here:

In case you didn't know, closures are like nominal structs that the compiler generates. Let's try that with your example and see what happens.

    let mut words: Vec<&str> = vec![];
    let mut push_str = |s:&str| { words.push(s) };

You're capturing a mutable borrow to your Vec<&str>:

struct Closure<'capture, 'a> {
    vec: &'capture mut Vec<&'a str>,
}

And to be useful, it also needs to implement some of the Fn traits. You can do this on nightly, but we'll just use a simplified version.

// Simplified version of the `Fn` closures
trait MyFn<Arg> {
    type Ret;
    fn my_call(&mut self, arg: Arg) -> Self::Ret;
}

impl<'a> MyFn<&'a str> for Closure<'_, 'a> {
    type Ret = ();
    fn my_call(&mut self, arg: &'a str) -> Self::Ret {
        self.vec.push(arg);
    }
}

Note how Closure<'_, 'a> only implements MyFn<&'a str>, where the lifetimes match. Otherwise we'd be trying to push something with the wrong lifetime into the Vec.

// This version will fail, because the lifetimes don't match!
impl<'a, 'any> MyFn<&'a str> for Closure<'_, 'any> {
    type Ret = ();
    fn my_call(&mut self, arg: &'a str) -> Self::Ret {
        self.vec.push(arg);
    }
}

In contrast, this implementation can take any lifetime as an argument -- it just uses and then discards the argument, there's no reason for any lifetimes to have to match or the like.

struct Other<'b>(&'b str);
impl<'a, 'b> MyFn<&'a str> for Other<'b> {
    type Ret = ();
    fn my_call(&mut self, arg: &'a str) -> Self::Ret {
        println!("{}, {}", self.0, arg);
    }
}

Now let's talk about how the trait bounds work:

fn test_not_higher_ranked<'a, F: MyFn<&'a str>>() {}
fn test_higher_ranked<F: for<'any> MyFn<&'any str>>() {}

The first says that there just has to be some lifetime 'a such that F can take &'a str as an argument. The second says that F has to take any lifetime as an argument. Like we just talked about, Other<'b> can take any lifetime, but Closure<'cap, 'a> can only take 'a.

So:

fn test<'a: 'cap, 'b, 'cap>() {
    // This works: `Other<'b>` can take `&str` with any lifetime
    test_higher_ranked::<Other<'b>>();

    // This works, because a lifetime exists such that 
    // `Closure<'cap, 'a>` can take `&'some_lifetime str`.
    // Namely, it can take `&'a str`.
    test_not_higher_ranked::<Closure<'cap, 'a>>();
    
    // This fails because it can *only* take `&'a str`.
    // It cant take a `&str` with any other lifetime.
    // test_higher_ranked::<Closure<'cap, 'a>>();

    // (This also works because if you can take any lifetime,
    // you can take one specific lifetime.)
    test_not_higher_ranked::<Other<'b>>();
}

Hopefully that helps to explain the bounds.


When you wrote this:

    let mut push_str = |s:&str| { words.push(s) };

The compiler took a that as a sign to try to implement something akin to this:

impl<'a, 'any> MyFn<&'a str> for Closure<'_, 'any> {

and as discussed, that can't work, and so you got an error. The compiler made an incorrect inference about what you wanted. @kpreid's constrain example is a way to nudge the compiler to infer what you wanted: a closure that need only accept a single lifetime, not any lifetime. It's a hack we use until we get better inference or a way to annotate what we want more directly.

You generally don't need or want lifetime bounds like F: ... + 'b on closures themselves, unless perhaps you're storing them. Note also that 'b: 'a and F: 'b means that F: 'a must also hold.

Let's see what happens if we use that lifetime bound on our example.[1]

fn over_constrain<'a, 'b:'a, F: MyFn<&'a str> + 'b>(f: F) -> F { f }

fn main() {
    let mut words: Vec<&str> = vec![];
    let mut push_str = over_constrain(Closure { vec: &mut words });
    push_str.my_call("a");
    push_str.my_call("b");

    words.push("more");
}
error[E0499]: cannot borrow `words` as mutable more than once at a time
  --> src/main.rs:52:5
   |
48 |     let mut push_str = over_constrain(Closure { vec: &mut words });
   |                                                      ---------- first mutable borrow occurs here
...
52 |     words.push("more");
   |     ^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

What happened?

The only way our Closure<'cap, 'a> can meet the F: 'a bound is if 'cap: 'a. And remember, we have a &'cap mut Vec<'a str>.

This would mean we have a &'a mut Vec<'a str> which is never what you want, because it borrows the Vec forever.

We don't care how long the closure itself is valid, we're just going to use it and get rid of it. So you don't need a bound on the closure itself, and in this case it was actively harmful, so you don't want a bound on the closure itself.


In other terms, F: MyFn<&'a str> + 'a means that the closure itself has to remain valid everwhere its arguments are valid. When lifetimes are allowed to shrink (when they are covariant), this is sometimes workable. But when mutation is involved, lifetimes usually aren't allowed to shrink (they are invariant). The mutation here makes 'a in Captures<'cap, 'a> invariant -- because Captures holds a &mut _ to our Vec. That's an exclusive borrow of our Vec.

So what we're saying with the bound is

  • "so long as the Vec is valid, the closure is valid"
  • "so long as the Vec is valid, the closure still exclusively borrows our Vec"
  • "the Vec is exclusively borrowed for as long as the Vec is valid"
  • "...we can never use our Vec directly again :frowning:"

  1. Or here it is in actual closure form. ↩︎

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.