0
votes

In my tests I had a helper function that runs a given method on differently configured objects, with a simplified version looking like this:

fn run_method<F>(f: F)
where
    F: Fn(&Foo),
{
    let to_test = vec![0i32];
    to_test
        .iter()
        .map(|param| {
            let foo = Foo(*param);
            f(&foo);
        })
        .for_each(drop);
}

// run_method(Foo::run);

This worked fine until I added references to the tested struct, making it "lifetime-annotated" (for lack of a proper term, I mean Foo<'a>).

Now I get an error indicating, I think, that Rust doesn't want to accept a Foo::method as a function that can be used with any given lifetime (i.e. F: for<'a> Fn(&Foo<'a>)), as required by the closure:

error[E0631]: type mismatch in function arguments
--> src/main.rs:54:5
   |
3  |     fn run(&self) {
   |     ------------- found signature of `for<'r> fn(&'r Foo<'_>) -> _`
...
54 |     run_method(Foo::run);
   |     ^^^^^^^^^^ expected signature of `for<'r, 's> fn(&'r Foo<'s>) -> _`
   |
note: required by `run_method`
--> src/main.rs:44:1
   |
44 | fn run_method<F>(f: F) where F: Fn(&Foo) {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0271]: type mismatch resolving `for<'r, 's> <for<'t0> fn(&'t0 Foo<'_>) {Foo::<'_>::run} as std::ops::FnOnce<(&'r Foo<'s>,)>>::Output == ()`
--> src/main.rs:54:5
   |
54 |     run_method(Foo::run);
   |     ^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
|
note: required by `run_method`
--> src/main.rs:44:1
   |
44 | fn run_method<F>(f: F) where F: Fn(&Foo) {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I can work around the problem by avoiding closures (though I don't really understand how 'a gets constrained to be local to run_method - isn't the lifetime parameter supposed to be chosen by the caller?):

fn run_method<'a, F>(f: F)
where
    F: Fn(&Foo<'a>),
{
    let to_test = vec![&0i32];
    for param in to_test {
        let foo = Foo(param);
        f(&foo);
    }
}

Can I fix this without rewriting? If not - is there a reason why this shouldn't work?

Complete code:

struct Foo<'a>(&'a i32);
impl<'a> Foo<'a> {
    fn run(&self) {
        println!("Hello {}", self.0);
    }
}

fn run_method<F>(f: F)
where
    F: Fn(&Foo),
{
    //same as  F: for<'a> Fn(&Foo<'a>) {
    let to_test = vec![0i32];
    to_test
        .iter()
        .map(|param| {
            let foo = Foo(param);
            f(&foo);
        })
        .for_each(drop);
}

fn main() {
    run_method(Foo::run);
}

// This works:
// fn run_method<'a, F>(f: F)
// where
//     F: Fn(&Foo<'a>),
// {
//     let to_test = vec![&0i32];
//     for param in to_test {
//         let foo = Foo(param);
//         f(&foo);
//     }
// }

// The lifetime-less version:
// struct Foo(i32);
// impl Foo {
//     fn run(&self) {
//         println!("Hello {}", self.0);
//     }
// }
// 
// fn run_parser_method<F>(f: F)
// where
//     F: Fn(&Foo),
// {
//     let to_test = vec![0i32];
//     to_test
//         .iter()
//         .map(|param| {
//             let foo = Foo(*param);
//             f(&foo);
//         })
//         .for_each(drop);
// }
// 
// fn main() {
//     run_parser_method(Foo::run);
// }

playground

An overview of other questions about the same error code:

1
It would be good if you took the time to show what research you have done before asking the question. One possible way that could have occurred is by reading the numerous questions that have been asked about the exact same error message, linking to them, and explaining why they are different.Shepmaster
@Shepmaster Out of respect for your contributions here, I've added the requested information, though I don't think it was necessary. I wonder what prompted you to make such a comment, though, as it felt unnecesarily harsh. Did this look like a question asked without doing any research? Did you mean that there was another question that answers or at least hints at an answer? (And why remove the formatting, did it make the question harder to read?)Nickolay
run_method(|foo| foo.run()); - that's itLaney
@Laney that works, thanks! Still unsure if there's anything wrong with passing the method directly (as clippy insists I do).Nickolay

1 Answers

1
votes

I can work around the problem by avoiding closures (though I don't really understand how 'a gets constrained to be local to run_method - isn't the lifetime parameter supposed to be chosen by the caller?)

It is. But when you rewrite it without closures, you have also put the reference inside the vec! invocation, so it is no longer constructed at runtime. Instead, the compiler can infer that to_test has type Vec<&'static i32>, and as 'static outlives any caller-chosen lifetime, there's no violation.

This fails to compile as you expect:

let to_test = vec![0i32];
for param in to_test.iter() {
    let foo = Foo(param);
    f(&foo);
}

is there a reason why this shouldn't work?

Yes, because the type of run is constrained by the type of Foo. More specifically, you have a lifetime decided by the caller (implicitly, in the type for Foo), so you have to construct a Foo of that lifetime to invoke the given run reference on it.

Can I fix this without rewriting?

That depends.

If all your test values are literals, you can make 'static references.

If you're able and willing to rewrite run, it's possible to make it unconstrained by the type of Foo

impl<'a> Foo<'a> {
    fn run<'s>(_self: &Foo<'s>) {
        println!("Hello {}", _self.0);
    }
}

But then it doesn't need to go in an impl block at all.

I think the solution provided in the comments is your best bet, because given that you don't care about your concrete Foo<'a> type, there's no need to give the method reference for that type.