I have the following code (minimal example of my real world code) and I don't understand how to tell the compiler that the lifetimes here are all fine. The lifetime 'b in &'a mut Foo<'b> is outliving 'a. I can guarantee that in do_with_foo, but I don't know how to tell the compiler. I've tried the signature do_with_foo<F: for<'a> Fn(&'a mut Foo<'a>)>(callback: F) instead, but then the compiler won't let me call foo.print() after the callback.
struct Foo<'a> {
content: &'a str,
print_count: u8,
}
impl<'a> Foo<'a> {
fn print(&mut self) {
println!("Call {}: {}", self.print_count, self.content);
self.print_count += 1;
}
}
fn do_with_foo<F: Fn(&mut Foo)>(callback: F) {
let content = String::from("Hello, Foo!");
let mut foo = Foo {
content: content.as_str(),
print_count: 1,
};
callback(&mut foo);
print!("From do_with_foo inside: ");
foo.print();
}
fn main() {
// do_with_foo(Foo::print);
// -> error:
// error[E0308]: mismatched types
// --> src/main.rs:26:5
// |
// 26 | do_with_foo(Foo::print);
// | ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
// |
// = note: expected trait `for<'a, 'b> Fn<(&'a mut Foo<'b>,)>`
// found trait `for<'a> Fn<(&'a mut Foo<'_>,)>`
do_with_foo(|foo| Foo::print(foo));
// -> works fine
}
That'd be a legitimate problem, if it were true. This would mean that you could have a long-living reference to a short-living one, which can make the outer reference dangling. It is the lifetime 'b that should outlive &'a instead.
However, I don't think this is what happens here. The code compiles just fine using a closure, and the two are (or should be) equivalent, since no coercion happens in either the argument or the return type.
This is a known issue as far as I know (I've certainly seen it at least twice in my own code in the past), and I haven't found any fix for it. You'll have to type out the redundant closure for now, I'm afraid.
The problem is that, when you write Foo::run, it's expanded into Foo::<'_>::run instead of for<'a> Foo::<'a>::run. So it's more a early-bound vs late bound problem. See even simpler repro below:
// we add `'a: 'a` so 'a become early-bound
fn run<'a: 'a>(_: &'a ()) {}
fn do<F: Fn(&())>(_: F) {}
fn main() {
// run become `run::<'_>` instead of `for<'a> run<'a>`
do(run); // Error
do(|x| run(x)); // Ok
}
There's some movement around making more lifetimes late bound in the repo, but I don't think those apply to this case. The Self type has specific lifetime, so methods (with some sort of self receiver) can't be generic / higher-ranked over the type's parameters. The language would need some higher-level reasoning to automatically move binders between levels, more or less emulating the closure or helper functions as a coercion or such.