Rust API中async块创建的Future不满足Send trait的问题解决
Rust API 异步Future Send安全错误修复方案
编译错误信息
error: future cannot be sent between threads safely --> src/bin/server/server.rs:115:5 | 115 | / { 116 | | info!("identity_create_post({:?}) - X-Span-ID: {:?}", id_create_request, context.get().0.clone()); 117 | | let did: String = match id::create_identity(format!("./{}.hodl", id_create_request.user_id.unwrap()).as_str(), id_create_request.stronghold_password.unwrap().as_str()).await { 118 | | Ok(did) => did, ... | 121 | | Ok(IdentityCreatePostResponse::TheIdentityHasBeenCreatedSuccessfully(models::IdCreateResponse{did:Some(did)})) 122 | | } | |_____^ future created by async block is not `Send` | = help: the trait `std::marker::Send` is not implemented for `dyn futures::Future<Output = Result<std::string::String, JwkStorageDocumentError>>` note: future is not `Send` as it awaits another future which is not `Send` --> src/bin/server/id.rs:49:26 | 49 | let fragment = match document | __________________________^ 50 | | .generate_method( 51 | | &storage, 52 | | KeyType::from_static_str("Ed25519"), ... | 55 | | MethodScope::VerificationMethod, 56 | | ).await { | |_____^ await occurs here on type `Pin<Box<dyn futures::Future<Output = Result<std::string::String, JwkStorageDocumentError>>>>`, which is not `Send` = note: required for the cast from `Pin<Box<{async block@src/bin/server/server.rs:115:5: 122:6}>>` to `Pin<Box<dyn futures::Future<Output = Result<IdentityCreatePostResponse, ApiError>> + std::marker::Send>>
问题场景
API使用#[async_trait]实现trait,异步函数要求返回的Future必须实现Send(异步trait默认会将返回Future装箱为Pin<Box<dyn Future + Send>>)。但内部调用的第三方函数generate_method返回的Future未实现Send,导致上层异步块无法满足Send要求,触发编译错误。
API核心实现代码:
#[async_trait] impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync { /// 创建账本身份 async fn identity_create_post( &self, id_create_request: models::IdCreateRequest, context: &C) -> Result<IdentityCreatePostResponse, ApiError> { info!("identity_create_post({:?}) - X-Span-ID: {:?}", id_create_request, context.get().0.clone()); let identifier: String = match id::create_identity(format!("./{}.keys", id_create_request.user_id.unwrap()).as_str(), id_create_request.stronghold_password.unwrap().as_str()).await { Ok(identifier) => identifier, Err(e) => {return Err(ApiError(format!("创建身份失败: {:?}",e).to_string()))}, }; Ok(IdentityCreatePostResponse::TheIdentityHasBeenCreatedSuccessfully(models::IdCreateResponse{did:Some(did)})) } ...
引发错误的内部函数片段:
let fragment = match document .generate_method( &storage, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, None, MethodScope::VerificationMethod, ).await { Ok(fragment) => fragment, Err(_) => panic!(), }; println!("----------------------------- {:?}",document); drop(fragment);
generate_method为第三方库函数,无法修改。
解决方案
核心思路是将非Send的Future限制在单线程上下文执行,避免跨线程传递,同时让上层函数返回的Future满足Send要求。以下是基于Tokio runtime的修改方案:
修改id::create_identity函数,使用LocalSet封装非Send的异步操作:
use tokio::task; // 引入你的其他依赖,比如JwkStorageDocumentError、KeyType等 pub async fn create_identity(storage_path: &str, password: &str) -> Result<String, JwkStorageDocumentError> { // 初始化document、storage的逻辑(保持原有代码) let (document, storage) = /* 原有初始化代码 */; // 将非Send的异步操作放在LocalSet中执行 task::LocalSet::new().run_until(async move { let fragment = document .generate_method( &storage, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, None, MethodScope::VerificationMethod, ) .await?; println!("----------------------------- {:?}", document); drop(fragment); // 执行生成最终did的逻辑(保持原有代码) let did = /* 原有生成did的代码 */; Ok(did) }).await }
原理说明
LocalSet创建了一个仅在当前线程运行的任务集合,非Send的Future可以在此安全执行,不会触发跨线程传递问题。run_until方法返回的Future本身实现了Send,因为它封装了在本地线程执行的逻辑,上层调用时可以安全跨线程调度。
如果使用futures库而非Tokio,可改用LocalPool实现类似逻辑:
use futures::executor::LocalPool; use futures::task::SpawnExt; use tokio::sync::oneshot; pub async fn create_identity(storage_path: &str, password: &str) -> Result<String, JwkStorageDocumentError> { // 初始化document、storage的逻辑(保持原有代码) let (document, storage) = /* 原有初始化代码 */; let mut pool = LocalPool::new(); let spawner = pool.spawner(); let (sender, receiver) = oneshot::channel(); // 将非Send任务提交到LocalPool spawner.spawn(async move { let result = async { let fragment = document .generate_method( &storage, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, None, MethodScope::VerificationMethod, ) .await?; println!("----------------------------- {:?}", document); drop(fragment); let did = /* 原有生成did的代码 */; Ok(did) }.await; // 发送结果到上层 let _ = sender.send(result); }).unwrap(); // 等待结果并返回 pool.run_until(receiver).await.unwrap() }
内容的提问来源于stack exchange,提问作者sandevins
相关产品推荐
相关产品推荐

