tokio-rs / tracing

Application level tracing for Rust.
https://tracing.rs
MIT License
5.45k stars 717 forks source link

`#[instrument]` breaks coercion in boxed future return types #1840

Open Alfred-Mountfield opened 2 years ago

Alfred-Mountfield commented 2 years ago

Bug Report

Version

0.1.29

Platform

Description

I added tracing::instrument to some of our code and surprisingly it broke compilation. I've tried to create a minimum reproducible example:

use std::future::Future;
use tokio::task::JoinError;
use tracing::instrument;

struct Dummy {}

impl Dummy {
    #[instrument(skip_all)]
    pub async fn _run_impl(&mut self) -> Box<dyn Future<Output = Result<(), JoinError>> + Send> {
        // some other condition
        if false {
            return Box::new(async move { Ok(()) });
        }

        let f = async move { _run().await };
        Box::new(tokio::task::spawn(f))
    }
}

async fn _run() {}

#[tokio::main]
async fn main() {
    let _bar = Dummy {};
}

Commenting/removing the instrument macro compiles, but with the macro you get the following error:

error[E0308]: mismatched types
  --> src/main.rs:18:18
   |
14 |             return Box::new(async move { Ok(()) });
   |                                        ---------- the expected `async` block
...
18 |         Box::new(tokio::task::spawn(f))
   |                  ^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found struct `tokio::task::JoinHandle`
   |
   = note: expected opaque type `impl Future<Output = [async output]>`
                   found struct `tokio::task::JoinHandle<()>`
note: return type inferred to be `impl Future<Output = [async output]>` here
  --> src/main.rs:14:20
   |
14 |             return Box::new(async move { Ok(()) });
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I assume this is to do with how the macro wraps the function in a closure, and that's somehow messing up the type coercion.

Here's a playground link that demonstrates it

Also, apologies if the issue description isn't accurate, I wasn't quite sure how to concisely explain it.

Potential Fixes

Wrapping the function within another one, and instrumenting that, seems to fix the error. Unsure of how this can be duplicated/used within the macro itself but thought I'd highlight (and here's a playground link):

use std::future::Future;
use tokio::task::JoinError;
use tracing::instrument;

struct Dummy {}

impl Dummy {
    #[instrument(skip_all)]
    pub async fn fn_wrapper(&mut self) -> Box<dyn Future<Output = Result<(), JoinError>> + Send> {
        self.run_impl().await
    }

    pub async fn run_impl(&mut self) -> Box<dyn Future<Output = Result<(), JoinError>> + Send> {
        // some other condition
        if false {
            return Box::new(async move { Ok(()) });
        }

        let f = async move { _run().await };
        Box::new(tokio::task::spawn(f))
    }
}

async fn _run() {}

#[tokio::main]
async fn main() {
    let _bar = Dummy {};
}
hawkw commented 2 years ago

Interesting. Thanks for the report --- the detailed reproduction is great. I'll see if I can figure out what's going on here.