如何为含动态成员的结构体适配Send与Sync,解决future无法跨线程安全发送问题
问题原因
报错的核心是async-trait的默认约束和你的trait对象缺少线程安全标记不匹配:
async-trait默认会为trait中的async方法返回的Future加上Send约束,保证Future可以在tokio这类多线程异步运行时的不同线程间传递。- 你的
drive是async方法,执行时会捕获&Automobile引用,要让返回的Future满足Send,要求&Automobile实现Send,而&T实现Send的前提是T实现Sync,也就是Automobile要满足Sync。 - 虽然
Arc本身是线程安全的,但它的Send/Sync实现是有条件的:只有Arc包裹的内容T满足Send + Sync时,Arc<T>才会实现Send + Sync。你声明的Arc<dyn PowerSource>中的trait对象没有加任何线程安全约束,默认不满足Send + Sync,导致Automobile整体不满足Sync/Send,最终触发编译错误。
修复方案
有两种常用修复方式,按需选择即可:
方案1:给trait对象添加Send + Sync约束(推荐,适配多线程异步运行时)
有两种写法可选:
写法A:给PowerSource trait添加超约束
要求所有实现PowerSource的类型都必须满足Send + Sync,后续使用trait对象时不需要额外加约束:
// 仅修改这一行,添加Send + Sync超约束 trait PowerSource: Send + Sync { fn supply_power(&self) -> Result<()>; }
完整可运行代码:
use std::{io::Result, sync::Arc}; use async_trait::async_trait; use tokio; // 给PowerSource加上Send + Sync超约束 trait PowerSource: Send + Sync { fn supply_power(&self) -> Result<()>; } struct ElectricMotor {} impl PowerSource for ElectricMotor { fn supply_power(&self) -> Result<()> { println!("ElectricMotor::supply_power"); Ok(()) } } #[async_trait] trait Vehicle { async fn drive(&self) -> Result<()>; } 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(()) }
写法B:仅在使用trait对象的位置加约束
如果不想强制所有PowerSource实现都满足线程安全,可以只在Automobile的字段声明处加约束:
struct Automobile { // 仅修改这一行,给dyn PowerSource加Send + Sync约束 power_source: Arc<dyn PowerSource + Send + Sync>, }
方案2:取消async-trait的默认Send约束(仅单线程异步场景使用)
如果你的代码只会运行在单线程异步运行时,不需要Future跨线程传递,可以给async_trait标记?Send取消默认的Send约束:
// 给trait和impl都加上?Send标记 #[async_trait(?Send)] trait Vehicle { async fn drive(&self) -> Result<()>; } #[async_trait(?Send)] impl Vehicle for Automobile { async fn drive(&self) -> Result<()> { self.power_source.supply_power()?; println!("Vehicle::Drive"); Ok(()) } }
注意这种方式生成的Future不能跨线程传递,无法在tokio的多线程调度器中正常使用。
内容的提问来源于stack exchange,提问作者Rudedog
相关产品推荐
相关产品推荐

