Lifetime of reference in Fn taking a reference as argument

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
}

(Playground)

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.

2 Likes

Thanks for the quick reply.

Sorry, I confused the two names and already edited my question before I saw you reply. It is the other way around, in fact. 'b outlives 'a.

Well that's unfortunate :frowning:. If this is a known issue, do you happen to know if there's a GitHub issue for it?

It's actually not a problem around outlive bounds or such. Here a simpler reproduce.

struct Foo<'a>(&'a ());

impl<'a> Foo<'a> {
    fn run(self) {}
}

fn do_with_foo<F: Fn(Foo)>(_: F) {}

fn main() {
    do_with_foo(Foo::run);
    do_with_foo(|x| Foo::run(x));
}

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
}
5 Likes

Another work around is to create a function that is higher-ranked over Foo's lifetime parameter.

impl<'a> Foo<'a> {
    fn print2(this: &mut Foo<'_>) {
        this.print()
    }
}

fn print_foo(foo: &mut Foo<'_>) {
    foo.print()
}

Playground.


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.

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.