如何在这种异步特性的异步方法中使用异步lambda



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

此回购中的代码是一个小型复制品。

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

但是,当我在异步特性中添加lambda作为异步函数的参数时,第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中,这是一种非常有用(而且很容易(的模式,可以在数据库事务中共享存储库代码和业务逻辑之间的元素:

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

这是在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将您的方法转换为返回类型已擦除的、虚拟调度的futureBox<dyn Future<Output = ...> + Send的方法。Send特性使创建的未来可在多线程执行器中使用,这些执行器在当前空闲的线程上轮询未来,这意味着他们必须将它们从一个线程移动到另一个线程。(如果你不使用多线程执行器,你可以选择退出该绑定。(

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

最新更新