Search code examples
rustrust-tokio

How to tell the compiler a value is not used after awaiting in an async_trait method?


In the example code below, a non-send value, Vec<T>, is moved into a function that returns something else. At this point, I no longer care about that vector. The returned object stores no reference to it, it no longer exists.

However, when I .await on the next line I get the error "captured value is not Send". Which it isn't, but since it should have been destroyed when vector_as_string exited, it doesn't need to send it across threads when the future restarts, because that variable is never used again.

use async_trait::async_trait;

async fn write_value(value: Vec<u8>) {
    println!("something")
}

fn vector_as_string<T>(vec: Vec<T>) -> Vec<u8> {
    Vec::new()
}

#[async_trait]
trait Writer {
    async fn write_array<T>(&mut self, value: Vec<T>);
}

pub struct WriterImplementation {}

#[async_trait]
impl Writer for WriterImplementation {
    async fn write_array<T>(&mut self, value: Vec<T>) {
        let string = vector_as_string(value);

        write_value(string).await
    }
}

#[tokio::main]
async fn main() {
    println!("Hi");
}

Dependencies:

[dependencies]
tokio = { version = "1.9.0", features = ["full"]}
async-trait = "0.1.51"

Error:

error: future cannot be sent between threads safely
  --> src/main.rs:20:55
   |
20 |       async fn write_array<T>(&mut self, value: Vec<T>) {
   |  _______________________________________________________^
21 | |         let string = vector_as_string(value);
22 | |
23 | |         write_value(string).await
24 | |     }
   | |_____^ future created by async block is not `Send`
   |
note: captured value is not `Send`
  --> src/main.rs:20:40
   |
20 |     async fn write_array<T>(&mut self, value: Vec<T>) {
   |                                        ^^^^^ has type `Vec<T>` which is not `Send`
   = note: required for the cast to the object type `dyn Future<Output = ()> + Send`
help: consider further restricting this bound
   |
20 |     async fn write_array<T + std::marker::Send>(&mut self, value: Vec<T>) {
   |                            ^^^^^^^^^^^^^^^^^^^

Adding T: Send as it suggests allows it to compile, but why does T need to be Send if we aren't holding any T's across the await, as it has already been moved?


Solution

  • From the async_trait documentation:

    Async fns get transformed into methods that return Pin<Box<dyn Future + Send + 'async>> and delegate to a private async freestanding function.

    Not all async traits need futures that are dyn Future + Send. To avoid having Send and Sync bounds placed on the async trait methods, invoke the async trait macro as #[async_trait(?Send)] on both the trait and the impl blocks.

    Applied to your case:

    #[async_trait(?Send)]
    trait Writer {
        async fn write_array<T>(&mut self, value: Vec<T>);
    }
    
    #[async_trait(?Send)]
    impl Writer for WriterImplementation {
        async fn write_array<T>(&mut self, value: Vec<T>) {
            let string = vector_as_string(value);
    
            write_value(string).await
        }
    }