Why do I see "expected bound lifetime parameter, found concrete lifetime" when closure does not specify explicit param types?

Hey all,

I currently have the following code (EDIT: Playground link):

use std::collections::BTreeMap;
use std::error::Error;

pub trait DeliveryConsumer: Send + Sync + Clone + Copy + 'static {
    fn handle(self, consumer_tag: &str, envelope: &String, properties: &BTreeMap<String, String>, body: &[u8]) -> Result<(), Box<Error>>;
}

impl<F> DeliveryConsumer for F
    where F: 'static + Send + Sync + Clone + Copy + FnOnce(&str, &String, &BTreeMap<String, String>, &[u8]) -> Result<(), Box<Error>>{
    fn handle(self, consumer_tag: &str, envelope: &String, properties: &BTreeMap<String, String>, body: &[u8]) -> Result<(), Box<Error>> {
        self(consumer_tag, envelope, properties, body)
    }
}

fn test(a: impl DeliveryConsumer) {
    let envelope = "foo".to_string();
    let map = BTreeMap::new();
    a.clone().handle("", &envelope, &map, &[0; 1]).unwrap();
}

fn test2() {
    let argh = "";
    test(move |consumer_tag/*: &str*/, envelope/*: &String*/, properties/*: &BTreeMap<String, String>*/, body/*: &[u8]*/| {
        println!("{}", argh);
        Ok(())
    })
}

Which produces the following compilation error:

Compiling playground v0.0.1 (/playground)
error[E0271]: type mismatch resolving `for<'r, 's, 't0, 't1> <[closure@src/lib.rs:23:10: 26:6 argh:_] as std::ops::FnOnce<(&'r str, &'s std::string::String, &'t0 std::collections::BTreeMap<std::string::String, std::string::String>, &'t1 [u8])>>::Output == std::result::Result<(), std::boxed::Box<(dyn std::error::Error + 'static)>>`
  --> src/lib.rs:23:5
   |
23 |     test(move |consumer_tag/*: &str*/, envelope/*: &String*/, properties/*: &BTreeMap<String, String>*/, body/*: &[u8]*/| {
   |     ^^^^ expected bound lifetime parameter, found concrete lifetime
   |
   = note: required because of the requirements on the impl of `DeliveryConsumer` for `[closure@src/lib.rs:23:10: 26:6 argh:_]`
note: required by `test`
  --> src/lib.rs:15:1
   |
15 | fn test(a: impl DeliveryConsumer) {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0631]: type mismatch in closure arguments
  --> src/lib.rs:23:5
   |
23 |     test(move |consumer_tag/*: &str*/, envelope/*: &String*/, properties/*: &BTreeMap<String, String>*/, body/*: &[u8]*/| {
   |     ^^^^ ---------------------------------------------------------------------------------------------------------------- found signature of `fn(_, _, _, _) -> _`
   |     |
   |     expected signature of `for<'r, 's, 't0, 't1> fn(&'r str, &'s std::string::String, &'t0 std::collections::BTreeMap<std::string::String, std::string::String>, &'t1 [u8]) -> _`
   |
   = note: required because of the requirements on the impl of `DeliveryConsumer` for `[closure@src/lib.rs:23:10: 26:6 argh:_]`
note: required by `test`
  --> src/lib.rs:15:1
   |
15 | fn test(a: impl DeliveryConsumer) {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

However, if I change test2 to look like this:

fn test2() {
    let argh = "";
    test(move |consumer_tag: &str, envelope: &String, properties: &BTreeMap<String, String>, body: &[u8]| {
        println!("{}", argh);
        Ok(())
    })
}

Then it all compiles correctly. I would have expected that the lifetime + closure argument types would be correctly implied here? It seems a bit strange to me that I need to explicitly specify the types in order for the lifetimes (which I haven't specified!) to be inferred correctly, but I might be missing what's actually happening here.

There might be some other issues here (e.g. do I really need to duplicate 'static + Send + ...; do I even need all those traits?; is this even the right way to achieve my actual goal?) but I'd like to figure this particular quirk out first, then look at the other questions!

I think this is Confusing type error due to strange inferred type for a closure argument · Issue #41078 · rust-lang/rust · GitHub

1 Like

Yes, I think you're right. This comment in particular seems to explain the cause: https://github.com/rust-lang/rust/issues/41078#issuecomment-358385901 -- if I change test to just take a Box<Fn(... instead of a DeliveryConsumer, the code then compiles. Though I think that would stop me from making the functions copyable, but that might not be a big deal.

Anyway, thanks for the link!

NB you can just write var: &_, to make the inference work correctly, you don't need to write out the entire reference type.

1 Like