繁体   English   中英

如何在此异步特征的异步方法中使用异步 lambda?

[英]How can I use an async lambda in an async method of this async trait?

我试图在 Rust 中重现我在 Golang 中使用 Clean Architecture 和 Repository 模式所做的事情。

这个 repo 中的代码是一个小的复制品

它适用于第一次第二次提交。

但是,当我在异步特征中添加 lambda 作为异步 function 的参数时, 第 60 行的错误是:

future cannot be sent between threads safely
the trait `std::marker::Sync` is not implemented for `dyn std::ops::FnMut(&'life3 Player) -> std::result::Result<Player, ()>`
required for the cast to the object type `dyn std::future::Future<Output = std::result::Result<(), ()>> + std::marker::Send`rustc
main.rs(59, 9): captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync`

我不明白该怎么做,我不知道这是否是正确的模式。

在 Golang 中,这是在数据库事务中共享存储库代码和业务逻辑之间的元素的一种非常有用(且简单)的模式:

  • 存储库 function 从 DB 获取播放器
  • 它用这个现有的播放器调用 lambda(或异步匿名 function,在 Golang 中没有像 Rust 中那样的闭包)
  • 业务逻辑代码在同一个数据库事务中与这个现有的播放器一起工作
  • 业务逻辑代码可以返回“更新的”播放器保存或错误到存储库代码
  • 存储库可以继续或中止数据库事务

这是在 Rust 中做事的正确方法吗?

如果没有,有什么替代方案?

如果是,如何修复此代码?

代码

  • main.rs:
use std::sync::{Arc, Mutex};

#[derive(Clone)]
struct Player {
    pub name: String,
    pub payed: bool,
}

#[async_trait::async_trait]
trait RepoMemory {
    async fn player_create(&self, name: &str, payed: bool) -> Result<Player, ()>;

    async fn player_delete(
        &self,
        id: &str,
        // lambda: impl FnMut(&Player) -> Result<Player, ()>,
        lambda: &dyn FnMut(&Player) -> Result<Player, ()>,
    ) -> Result<(), ()>;
}

struct InMemoryRepository {
    pub players: Mutex<Vec<Player>>,
}

impl InMemoryRepository {
    pub fn new() -> Self {
        Self {
            players: Mutex::new(vec![]),
        }
    }
}

#[async_trait::async_trait]
impl RepoMemory for InMemoryRepository {
    async fn player_create(&self, name: &str, payed: bool) -> Result<Player, ()> {
        let mut players = self.players.lock().unwrap();

        if players.iter().any(|player| player.name == name) {
            println!("Player {} already exists!", name);

            return Err(());
        }

        let new_player = Player {
            name: name.to_string(),
            payed,
        };

        players.push(new_player.clone());

        println!("Player {} created", &name);

        Ok(new_player)
    }

    async fn player_delete(
        &self,
        name: &str,
        lambda: &dyn FnMut(&Player) -> Result<Player, ()>,
    ) -> Result<(), ()> {
        let mut players = self.players.lock().unwrap();

        match players.iter().position(|player| player.name == name) {
            Some(index) => {
                let player = players.get(index).unwrap();

                lambda(player)?;

                players.remove(index);

                println!("Player {} deleted", name);

                Ok(())
            }
            None => {
                println!("Cannot find player {}!", name);

                Err(())
            }
        }
    }
}

struct CreateCommand {
    repo: Arc<dyn RepoMemory>,
}

impl CreateCommand {
    pub fn new(repo: Arc<dyn RepoMemory>) -> Self {
        Self { repo }
    }

    pub async fn execute(&self, name: &str, payed: bool) -> Result<Player, ()> {
        let created_player = self.repo.player_create(name, payed).await.unwrap();

        Ok(created_player)
    }
}

struct DeleteCommand {
    repo: Arc<dyn RepoMemory>,
}

impl DeleteCommand {
    pub fn new(repo: Arc<dyn RepoMemory>) -> Self {
        Self { repo }
    }

    #[allow(clippy::result_unit_err)]
    pub async fn execute(&self, name: &str) -> Result<bool, ()> {
        self.repo
            .player_delete(name, &|existing_player| {
                // I need to work with existing_player here, do some .await calls and return it to repo maybe, for example:

                if existing_player.payed {
                    println!("Cannot delete paying player {}!", name);

                    return Err(());
                }

                Ok(existing_player.clone())
            })
            .await?;

        Ok(true)
    }
}

struct Service {
    pub player_create: CreateCommand,
    pub player_delete: DeleteCommand,
}

fn new_service() -> Service {
    let repo_memory = Arc::new(InMemoryRepository::new());

    Service {
        player_create: CreateCommand::new(repo_memory.clone()),
        player_delete: DeleteCommand::new(repo_memory),
    }
}

#[tokio::main]
async fn main() {
    let service = new_service();

    let bob = Player {
        name: "bob".to_owned(),
        payed: true,
    };

    service
        .player_create
        .execute(&bob.name, bob.payed)
        .await
        .unwrap();

    service.player_delete.execute(&bob.name).await.unwrap();

    let john = Player {
        name: "john".to_owned(),
        payed: false,
    };

    service
        .player_create
        .execute(&john.name, john.payed)
        .await
        .unwrap();

    service.player_delete.execute(&john.name).await.unwrap();
}

在幕后async_trait将您的方法转换为返回类型擦除的虚拟分派未来Box<dyn Future<Output =...> + Send的方法。 Send trait 使创建的期货可用于多线程执行程序,后者在当前空闲的任何线程上轮询未来,这意味着他们必须将它们从一个线程移动到另一个线程。 (如果您不使用多线程执行程序,您可以选择退出该限制。)

Send绑定要求传递给方法的任何参数都是Send ,因为未来可以在一个线程中创建并在其他线程中轮询。 正如编译器所说,对于&dyn...引用为Send ,基础闭包必须是Sync ,您可以通过将适当的绑定添加到您收到的闭包来确保这一点。 这个界限并不限制你在实践中可以在闭包中做什么,因为你很少需要非Sync闭包。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM