How to overcome conflated compilation issues?

I just made a dumb mistake, accidentally leaving #[tokio::main] on two separate functions:

// main.rs
#[tokio::main]
async fn main() -> Result<(), lambda_extension::Error> {
    let g = route::init().await.unwrap();
    todo!()
}

// route.rs

#[tokio::main]
pub async fn init() -> Result<(), tokio::io::Error> {
    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root`
        .route("/", get(root))
        // `POST /users` goes to `create_user`
        .route("/users", post(create_user));

    // run our app with hyper, listening globally on port 3000
    let listener = tokio::net::TcpListener::bind("0.0.0.0:2772").await?;
    axum::serve(listener, app).await
}

This is my own fault, but the compiler was happy! So long as I removed the await in my main..?

Also, if I didn't remove the await it gave a suggestion I'm sure maintainers would agree doesn't look quite right (see the part where it suggests: "async async")

error[E0277]: `Result<(), std::io::Error>` is not a future
  --> src/main.rs:32:27
   |
32 |     let g = route::init().await.unwrap();
   |             ------------- ^^^^^ `Result<(), std::io::Error>` is not a future
   |             |
   |             this call returns `Result<(), std::io::Error>`
   |
   = help: the trait `Future` is not implemented for `Result<(), std::io::Error>`, which is required by `Result<(), std::io::Error>: IntoFuture`
   = note: Result<(), std::io::Error> must be a future or must implement `IntoFuture` to be awaited
   = note: required for `Result<(), std::io::Error>` to implement `IntoFuture`
help: remove the `.await`
   |
32 -     let g = route::init().await.unwrap();
32 +     let g = route::init().unwrap();
   |
help: alternatively, consider making `fn init` asynchronous
  --> src/route.rs:9:4
   |
9  | pub async async fn init() -> Result<(), tokio::io::Error> {

So the problem is, if it's 2 AM and my brain is not its best self, how can I actually debug the true underlying problem when I suspect the compiler is conflating two or more other issues and fumbling to articulate feedback? Are there tricks or is just finding the needle in the haystack, so to speak?

I eventually found it... but... it took an embarrasingly long time... :blush:

If you expand macros (under Tools) to see what the tokio::main does, this part probably becomes more clear: the function so macro'd is not actually an async fn at all! So init now returns an actual Result<..> and not a Future<Output = Result<..>> that can be awaited.

Related.

Unfortunately I don't know that there's any golden way around this general problem, since macros can do all sorts of whacky stuff.

Indeed, heh. I guess it's applying a hint to the pre-macro'd signature even though the error is due to the post-macro'd signature. Definitely a diagnostic failure. It'd be better if it showed you that init was actually not an async fn IMO (with a note that you're looking at macro outputted code). I don't know enough about proc macro spans to know how fixable that is.[1]

I didn't find an existing issue about async async specifically.[2] You could file one.


  1. Or if they want to, as it may degrade other errors, since async is full of making one color of function look like another. ↩︎

  2. I "only" skimmed 10 pages of issues... ↩︎

1 Like