You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 05:42:02