rust-lang / rust

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

Unclear "higher-ranked lifetime error" error #115525

Open DCNick3 opened 1 year ago

DCNick3 commented 1 year ago

Code

main.rs:

use tokio::io::{self, AsyncWriteExt};

pub async fn get_by_id<I, S>(ids: I) -> io::Result<()>
where
    I: IntoIterator<Item = S>,
    S: Into<String>,
{
    for s in ids {
        let s = s.into();
        let mut stdout = io::stdout();
        stdout.write_all(s.as_bytes()).await?;
    }

    Ok(())
}

async fn beba() -> io::Result<()> {
    let audios = vec![(1, 2)];
    let _audios = get_by_id(
        audios
            .iter()
            .map(|(a, b)| format!("{}_{}", a, b)),
    )
    .await?;

    Ok(())
}

trait FnTrait {}

impl<F, Fut> FnTrait for F
where
    F: Fn() -> Fut + Send + Sync + 'static,
    Fut: std::future::Future<Output = io::Result<()>> + Send + 'static,
{}

fn check_fn<F: FnTrait>(_f: F) {}

#[tokio::main]
async fn main() -> io::Result<()> {
    check_fn(beba);

    Ok(())
}

Cargo.toml:

[package]
name = "hrle-repro"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1.32.0", features = ["full"] }

Current output

error: higher-ranked lifetime error
  --> src/main.rs:41:5
   |
41 |     check_fn(beba);
   |     ^^^^^^^^^^^^^^
   |
   = note: could not prove `fn() -> impl Future<Output = Result<(), std::io::Error>> {beba}: FnTrait`

error: could not compile `hrle-repro` (bin "hrle-repro") due to previous error

Desired output

I am not sure of the exact wording, but it should explain that the failure is due to the Future returned from the function not implementing Send and Sync

Rationale and extra context

Without explanation of why does the passed function not implement the required trait, it is not immediately clear what the issue is.

Other cases

The error output is the same on stable and nightly

Anything else?

Originally minimized from this code using reqwest and teloxide

zjp-CN commented 9 months ago

The error now becomes

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:41:5
   |
41 |     check_fn(beba);
   |     ^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'0 (i32, i32)) -> String` must implement `FnOnce<(&'1 (i32, i32),)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(&(i32, i32),)>`

error: implementation of `Iterator` is not general enough
  --> src/main.rs:41:5
   |
41 |     check_fn(beba);
   |     ^^^^^^^^^^^^^^ implementation of `Iterator` is not general enough
   |
   = note: `Iterator` would have to be implemented for the type `std::slice::Iter<'0, (i32, i32)>`, for any lifetime `'0`...
   = note: ...but `Iterator` is actually implemented for the type `std::slice::Iter<'1, (i32, i32)>`, for some specific lifetime `'1`

It's still not clear what happened. But I'll mimimize as follows which emits the same errors implementation of `FnOnce`/`Iterator` is not general enough.

use std::future::Future;

pub async fn get_by_id<I>(ids: I)
where
    I: IntoIterator<Item = String>,
{
    for s in ids {
        async { s.as_bytes() }.await;
    }
}

async fn beba() {
    get_by_id([1].iter().map(|_| String::new())).await;
}

trait FnTrait {}

impl<F, Fut> FnTrait for F
where
    F: Fn() -> Fut + Send + Sync + 'static,
    Fut: Future<Output = ()> + Send + 'static,
{
}

fn check_fn<F: FnTrait>(_f: F) {}

fn main() {
    check_fn(beba);
}

To make the code above compile (in some sense), you can do one of these

For OP in question, by applying the third way, it compiles.

jorendorff commented 9 months ago

I have a similar case, where a mysterious error, triggered while the compiler is trying to prove something is Send, is presented without that context.

This one doesn't have any dependencies. Same behavior in Stable and Nightly.

   Compiling playground v0.0.1 (/playground)
error: implementation of `From` is not general enough
  --> src/lib.rs:67:5
   |
67 |     spawn(handle_connection());
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `From` is not general enough
   |
   = note: `Box<(dyn std::error::Error + Send + Sync + 'static)>` must implement `From<Box<(dyn std::error::Error + Send + Sync + '0)>>`, for any lifetime `'0`...
   = note: ...but it actually implements `From<Box<(dyn std::error::Error + Send + Sync + 'static)>>`

error: could not compile `playground` (lib) due to 1 previous error

This one can be solved by refactoring the body of handle_connection a little. But the issue is the error message.

forum post about this one

jorendorff commented 9 months ago

...Mine at least is probably a duplicate of #102211.