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()
}
dyn is both ugly and makes me wonder about what it actually does.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
dynis 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));