How to adopt Send and Sync for a struct with a dynamic member (future cannot be sent between threads safely)

Viewed 1615

Consider the following code which declares a trait with an async method using the async-trait crate.

use std::{io::Result, sync::Arc};

use async_trait::async_trait;
use tokio;

// A trait that describes a power source.
trait PowerSource {
    fn supply_power(&self) -> Result<()>;
}

// ElectricMotor implements PowerSource.
struct ElectricMotor {}

impl PowerSource for ElectricMotor {
    fn supply_power(&self) -> Result<()> {
        println!("ElectricMotor::supply_power");
        Ok(())
    }
}

// A trait that describes a vehicle
#[async_trait]
trait Vehicle {
    async fn drive(&self) -> Result<()>;
}

// An automobile has some kind of power source and implements Vehicle
struct Automobile {
    power_source: Arc<dyn PowerSource>,
}

#[async_trait]
impl Vehicle for Automobile {
    async fn drive(&self) -> Result<()> {
        self.power_source.supply_power()?;
        println!("Vehicle::Drive");
        Ok(())
    }
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let driver = ElectricMotor {};
    let controller = Automobile {
        power_source: Arc::new(driver),
    };

    controller.drive().await?;

    Ok(())
}

This doesn't compile with error "future cannot be sent between threads safely":

error: future cannot be sent between threads safely
  --> src/main.rs:34:41
   |
34 |       async fn drive(&self) -> Result<()> {
   |  _________________________________________^
35 | |         self.power_source.supply_power()?;
36 | |         println!("Vehicle::Drive");
37 | |         Ok(())
38 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: the trait `Sync` is not implemented for `(dyn PowerSource + 'static)`
note: captured value is not `Send`
  --> src/main.rs:34:21
   |
34 |     async fn drive(&self) -> Result<()> {
   |                     ^^^^ has type `&Automobile` which is not `Send`
   = note: required for the cast to the object type `dyn Future<Output = Result<(), std::io::Error>> + Send`

error: future cannot be sent between threads safely
  --> src/main.rs:34:41
   |
34 |       async fn drive(&self) -> Result<()> {
   |  _________________________________________^
35 | |         self.power_source.supply_power()?;
36 | |         println!("Vehicle::Drive");
37 | |         Ok(())
38 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: the trait `Send` is not implemented for `(dyn PowerSource + 'static)`
note: captured value is not `Send`
  --> src/main.rs:34:21
   |
34 |     async fn drive(&self) -> Result<()> {
   |                     ^^^^ has type `&Automobile` which is not `Send`
   = note: required for the cast to the object type `dyn Future<Output = Result<(), std::io::Error>> + Send`

If I understand the error correctly, it thinks that Automobile is not Send because the power_source property is not and so it can't create a proper future. My understanding is that Arc is thread safe and implements Send and Sync, but I am still very new to Rust concurrency and still not completely clear on what that means.

How would I fix this error?

1 Answers

You have to modify your Automobile definition:

struct Automobile {
    power_source: Arc<dyn PowerSource>,
}

In rust a type is Send if and only if all its members are Send (unless you manually & unsafely implement Send). The same applies for Sync. So given that your power_source is not Send, then the generated impl Future will also not be Send.

The way to fix it is to add a Send + Sync requirement for power_source:

struct Automobile {
    power_source: Arc<dyn PowerSource + Send + Sync>,
}

But why Sync you might ask ? The compiler is complaining only for Send after all. The reason it requires Sync is because Arc<T> implements Send only if T is both Send and Sync

Additional reading:

Related