3

What does it mean when Rust complains that two equal types don't match?

The following error appears to be comparing the type...

<for<'_> fn(&u32) -> impl futures::Future<Output = u32> {f} as FnOnce<(&u32,)>>::Output

...to itself.

error[E0308]: mismatched types
  --> src/main.rs:31:18
   |
31 |     let output = map(f, input);
   |                  ^^^ lifetime mismatch
   |
   = note: expected associated type `<for<'_> fn(&u32) -> impl futures::Future<Output = u32> {f} as FnOnce<(&u32,)>>::Output`
              found associated type `<for<'_> fn(&u32) -> impl futures::Future<Output = u32> {f} as FnOnce<(&u32,)>>::Output`
   = note: the required lifetime does not necessarily outlive the empty lifetime
note: the lifetime requirement is introduced here
  --> src/main.rs:6:39
   |
6  | pub fn map<U, V, W>(f: impl Fn(&U) -> W, items: Vec<U>) -> impl futures::Stream<Item = V>
   |                                       ^

I've reduced it to the following minimal example:

use futures::stream::{FuturesUnordered, StreamExt};
use async_stream::stream;

pub fn map<U, V, W>(f: impl Fn(&U) -> W, items: Vec<U>) -> impl futures::Stream<Item = V>
where V: Send, W: futures::Future<Output = V> + Send
{
    stream! {
        let mut futures = FuturesUnordered::new();
        let mut i = 2;
        if 2 <= items.len() {
            futures.push(tokio::spawn(f(&items[0])));
            futures.push(tokio::spawn(f(&items[1])));
            while let Some(result) = futures.next().await {
                let y = result.unwrap();
                yield y;
                futures.push(tokio::spawn(f(&items[i])));
                i += 1
            }
        }
    }
}

#[tokio::main]
async fn main() {
    async fn f(x: &u32) -> u32 {
        x + 1
    }
    let input = vec![1, 2, 3];
    let output = map(f, input);
    futures::pin_mut!(output);
    while let Some(x) = output.next().await {
        println!("{:?}", x);
    }
}
Test
  • 962
  • 9
  • 26

1 Answers1

1

It means they are not equal, only displayed so, and rustc has omitted some important details.

In this case, the important information omitted is one lifetime, and let me annotate it:

expected associated type `<for<'_> fn(&u32) -> impl futures::Future<Output = u32> {f} as FnOnce<(&u32,)>>::Output`
   found associated type `<for<'_> fn(&u32) -> impl futures::Future<Output = u32> + '_ {f} as FnOnce<(&u32,)>>::Output + '_`

Or with names,

expected associated type `<for<'a> fn(&'a u32) -> impl futures::Future<Output = u32> {f} as FnOnce<(&'a u32,)>>::Output`
   found associated type `<for<'a> fn(&'a u32) -> impl futures::Future<Output = u32> + 'a {f} as FnOnce<(&'a u32,)>>::Output + 'a`

This is because the async fn f() is desugared into:

fn f<'a>(x: &'a u32) -> impl futures::Future<Output = u32> + 'a {
    async move { x + 1 }
}

That is, the resulting future depends on the argument's lifetime (because it captures it). However, map() expects it to not, since it is a standalone generic paramater (W).

For more and potential solutions, see Lifetime of a reference passed to async callback.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • Wow ok thank you. Why does the compiler display them the same? – Test Jun 17 '22 at 01:47
  • I have spent a week trying to write a simple function and am on the verge of abandoning Rust. This is hell. Can you show how to write this function using a box? – Test Jun 17 '22 at 01:57
  • @Test Usually because it can't otherwise. The syntax I posed is not a valid Rust syntax. – Chayim Friedman Jun 17 '22 at 02:01
  • This is hell. This isn't because I haven't taken time to learn Rust. I've read the entire book and done the little exercises and rustlings. This was nowhere in it. I even tried adapting the Pin> from the answers linked in your linked answer. How is it to be done? – Test Jun 17 '22 at 02:01
  • @Test Something like `f: impl Fn(&U) -> Pin + '_>>` and `fn f(x: &u32) -> Pin + '_>> { Box::pin(async move { x + 1 }) }`. – Chayim Friedman Jun 17 '22 at 02:04
  • @Test Indeed, this kind of issues requires deep understanding of Rust and experience to solve. My recommendation is to not touch async Rust at the beginning; sync Rust imposes much less challenges like that. – Chayim Friedman Jun 17 '22 at 02:05
  • Putting that in doesn't work, and I have no idea how to interpret the error messages I'm getting. If the error message actually hides the real problem, I have no idea what to do here. If the solution is straightforward when one knows Rust, do you mind simply putting that into the code? – Test Jun 17 '22 at 02:10
  • I have to do async: the only draw of Rust is the performance. I write Haskell and OCaml professionally. How can type errors be this hard? – Test Jun 17 '22 at 02:10
  • If it is straightforward once you've got experience, do you mind putting your suggestion in the example? I can't figure it out. – Test Jun 17 '22 at 02:14
  • @Test https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=005f9509660834ce7f390eba28e136d9. This will still not work with the actual code because `tokio::spawn()` requires `'static`. – Chayim Friedman Jun 17 '22 at 02:15
  • @Test I didn't mean to never write async, just defer it until you become experienced enough with the language. And you won't have this type error in OCaml and Haskell since they don't have lifetimes, they're GCed. – Chayim Friedman Jun 17 '22 at 02:16
  • Yes, but...can you solve it? I'm starting to think I should just tell the teamlead we shouldn't use Rust. – Test Jun 17 '22 at 02:19
  • @Test You can ask more SO questions if you have some concrete problem. But try to solve them yourself :) – Chayim Friedman Jun 17 '22 at 02:22
  • This is my concrete problem. I’ve been trying to create an async function that gets spawned. I’ve been trying for four days. I write Haskell and or OCaml professionally. I’ve read the entire book. I’ve read the answers to the questions you linked and tried to put them in my answer. Is it possible to solve? – Test Jun 17 '22 at 02:33