rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.37k stars 12.59k forks source link

async closure parameter type inference does not work anymore #127425

Closed peku33 closed 2 months ago

peku33 commented 3 months ago

When using async closures, rust since around 1.80 fails to infer parameter type in most scenarios.

#![feature(async_closure)]

use anyhow::Error;
use futures::{stream::repeat_with, StreamExt};

fn accept_str(_: &str) {}
fn accept_string(_: &String) {}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Error> {
    // when using closure returning async block - everything works
    repeat_with(|| "foo".to_owned())
        .take(1)
        .for_each(|value| async move {
            accept_str(&value); // ok
            accept_str(value.as_str()); // ok
            accept_string(&value); // ok
        })
        .await;

    // when using async closure, most options fail
    repeat_with(|| "foo".to_owned())
        .take(1)
        .for_each(async move |value| {
            // error on whole closure, rust thinks that type of value is `str`
            // accept_str(&value);

            // type annotations needed, cannot infer type
            // accept_str(value.as_str());

            // this works
            accept_string(&value); // ok
        })
        .await;

    // can be fixed by providing type hint on async closure parameter
    repeat_with(|| "foo".to_owned())
        .take(1)
        .for_each(async move |value: String| {
            accept_str(&value); // ok
            accept_str(value.as_str()); // ok
            accept_string(&value); // ok
        })
        .await;

    Ok(())
}

I expected to see this happen: All options used to work in past versions of rust

Instead, this happened: Without explicit type hint it fails

Version it worked on

It used to work in the past, around 1.79

Version with regression

rustc 1.81.0-nightly (524d806c6 2024-07-05)
binary: rustc
commit-hash: 524d806c62a82ecc0cf8634b94997ae506f4d6f9
commit-date: 2024-07-05
host: x86_64-pc-windows-msvc
release: 1.81.0-nightly
LLVM version: 18.1.7
compiler-errors commented 3 months ago

We don't typically mark nightly features with regression-* labels, so I'm removing those.

Are you certain this regressed in 1.80? This should have regressed in #120361, when I reimplemented async closures completely.

peku33 commented 3 months ago

I am not 100% sure when it happened. I used to work with 1.7x a couple of months ago. Then I left my project for a moment. When I came back, after updating to 1.81 it no longer works. IIRC before leaving it was around 1.79, but I'm not sure.

compiler-errors commented 3 months ago

This limitation to async closure signature inference has to do with the fact that the combinators you're using (e.g. StreamExt::for_each) take regular Fn trait bounds. Is there a particular reason you're using async closures here? They're really not gaining you anything other than a dependency on the nightly compiler.

peku33 commented 3 months ago

I posted a minimal example, indeed async does not make much sense here, but in project I work on it does. As a workaround I've just annotated parameters with original types, but I still believe this is a bug.

GrigorenkoPV commented 3 months ago

This should have regressed in #120361, when I reimplemented async closures completely.

This indeed regressed in #120361.

peku33 commented 2 months ago

While the problem from first message in issue seems to be fixed, there are still cases when this fails.

One of examples is when using try_for_each instead of for_each, when closure is expected to return Result:

#![feature(async_closure)]

use anyhow::Error;
use futures::{stream::repeat_with, StreamExt, TryStreamExt};

fn accept_str(_: &str) {}
fn accept_string(_: &String) {}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Error> {

    repeat_with(|| "foo".to_owned())
        .take(1)
        .map(Result::<_, Error>::Ok)
        .try_for_each(async move |value| {
            // error on whole closure, rust thinks that type of value is `str`
            accept_str(&value);

            // type annotations needed, cannot infer type
            accept_str(value.as_str());

            // this works
            accept_string(&value); // ok

            Ok(())
        })
        .await?;

    Ok(())
}

Should I open new issue, or this one will be reopened?

compiler-errors commented 2 months ago

Please open a new issue, since it's likely a different root cause