3
#[derive(Default, Serialize, Deserialize, Debug, Eq, Hash, PartialEq)]
pub struct Component {
    name: String,
    module: String,
    r#type: String,
    url: String,
    hash: Option<String>,
    #[serde(skip)]
    retied_times: i8,
}

struct Tasks<'a, F>
where
    F: Future<Output = Result<()>>,
{
    foo: fn(&'a str, &'a Component) -> F,
}

impl<'a, F> Tasks<'a, F>
where
    F: Future<Output = Result<()>>,
{
    pub async fn call(&self, target_dir: &'a str, component: &'a Component) {
        (task)(target_dir, component).await;
    }
}
pub async fn process(target_dir: &str, component: &Component) {
    let task = Tasks { foo: download };
    task.call(target_dir, component);
}

async fn download(target_dir: &str, component: &Component) -> Result<()> {
    //...
}

This code will works just fine.

But when I remove the lifetime 'a in the struct Tasks, I won't compile. The compile error is:

 --> src/lib.rs:28:29
   |
28 |     let task = Tasks { foo: download };
   |                             ^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'r, 's> fn(&'r str, &'s component::Component) -> _`
                 found fn item `for<'r, 's> fn(&'r str, &'s component::Component) -> impl Future<Output = Result<(), anyhow::Error>> {download}`

Why is this happening?

Aamir
  • 1,974
  • 1
  • 14
  • 18
lucian
  • 53
  • 4

1 Answers1

2

The future returned by an async function captures all of its lifetime parameters. That is, the following:

async fn download(target_dir: &str, component: &Component) -> Result<()> {
    // ...
}

Is actually a shortcut for the following:

fn download<'a, 'b>(target_dir: &'a str, component: &'b Component) -> impl Future<Output = Result<()>> + 'a + 'b {
    async move {
        // ...
    }
}

That is, the future is parameterized over the lifetimes too.

When you use 'a in Tasks, you instantiate download with 'a. That is, Tasks { foo: download } is actually Tasks { foo: download::<'a, 'a> } (not valid Rust, but the idea is clear). Its returned future type is also instantiated with 'a. So you got a concrete type fn(&'a str, &'a Component), and a concrete future type. This is working well.

However, when you leave 'a outside, your fn and Future types are still parametric. For the fn type, this is not a problem: we can use Higher-Ranked Trait Bounds (for<'a, 'b> fn(&'a str, &'b Component), or with lifetime elision just fn(&str, &Component)) to specify that. But with the future type, we cannot express it being parametric (at least, not easily; but see here). This is the error you saw: the compiler expected a concrete future type for F, but got a parametric ("more general") type for it.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77