2

I'm confused about what's going on with lifetimes below:

struct Foo{}
impl Foo {
    fn foo(&self, _s: &str) {}
}

fn main() {
    let foo = &Foo{};
    let closure = |s| foo.foo(s);

    // Part 1: error message about wrong lifetime in FnOnce
    take_closure(closure); 

    // Part 2: no error when inlined
    take_closure(|s| foo.foo(s));

    // Part 3: no error when `dyn`d and given explicit signature
    let closure: &dyn Fn(&str) -> _ = &|s| foo.foo(s);
    take_closure(closure);
}

fn take_closure(f: impl Fn(&str) -> ()) {
    let s = get_string();
    f(&s)
}

fn get_string() -> String {
    "".to_string()
}

playground

  1. Why does Part 1 error?
  2. Why does Part 2 not error?
  3. Why does Part 3 not error and what actually happens in part 3? Does Rust make a vtable? The LLVM outputs differ between 2 and 3
  4. Is there a better way? Inlining is ugly and dyn is both ugly and makes me wonder about what it actually does.
Max Heiber
  • 14,346
  • 12
  • 59
  • 97
  • This might be a duplicate of this post: https://stackoverflow.com/questions/52696907/why-does-passing-a-closure-to-function-which-accepts-a-function-pointer-not-work – emagers Feb 11 '22 at 23:00
  • @EricMagers That question is about function pointers, not closures – Max Heiber Feb 11 '22 at 23:19
  • 1
    Parts of this question are answered at [Expected bound lifetime parameter, found concrete lifetime](https://stackoverflow.com/q/31362206/3650362), [How to declare a higher-ranked lifetime for a closure argument?](https://stackoverflow.com/q/31403723/3650362), and [Returning a higher-kinded closure that captures a reference](https://stackoverflow.com/q/69048869/3650362). – trent Feb 12 '22 at 01:56

1 Answers1

7

Why does Part 1 error?

Rust's type inference is not great at deciding what type a closure should have, when the closure is declared separately from where it is used. When the closure accepts references, the compiler often assumes that there is some specific lifetime that will be involved, not “any lifetime the caller cares to provide” as is actually required here.

In fact, there's an active Rust RFC to improve this by adding another way to specify lifetime parameters on closures. (The RFC also contains an example where making the opposite lifetime assumption would not work.)

what actually happens in part 3? Does Rust make a vtable?

Yes, there's a vtable involved whenever you use dyn. That's not especially relevant to the root cause here; it's just that the elided lifetime in dyn Fn(&str) got resolved the way you needed rather than the way you didn't.

Is there a better way? Inlining is ugly and dyn is both ugly and makes me wonder about what it actually does.

Placing a closure directly in the function call expression that uses it is very common Rust style, and I recommend you stick to it whenever possible, since it's also the way that works well with type inference.

As a workaround in the case where you need to use a closure more than once, you could pass the closure through a function that constrains its type:

fn string_acceptor<F: Fn(&str) -> ()>(f: F) -> F {
    f
}

...

    let foo = &Foo{};
    let closure = string_acceptor(|s| foo.foo(s));
Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
  • Thanks for your answer and for the idea for using an intermediate function. Why does the `dyn` example work? Does `dyn` make the closure a trait object, so something here explains it? https://doc.rust-lang.org/stable/reference/lifetime-elision.html#default-trait-object-lifetimes – Max Heiber Feb 12 '22 at 10:39
  • Is there any performance implication to `dyn`ifying the closure? – Max Heiber Feb 12 '22 at 10:40
  • 1
    @MaxHeiber I don't have a good general explanation for how lifetimes are chosen. "Does dyn make the closure a trait object": yes. "so something here explains it?": not that section, that's about the lifetime of state _held by the closure_, not of its arguments. See [lifetime elision in functions](https://doc.rust-lang.org/stable/reference/lifetime-elision.html#lifetime-elision-in-functions) instead. "Is there any performance implication to `dyn`ifying the closure?" Certainly, but beware of assuming that it's significant. – Kevin Reid Feb 12 '22 at 16:29