#actor #tokio #async-await #async #actor-framework #actix #future

akt

为Rust和Tokio设计的actor库,可轻松与async/await消息处理器一起使用

3个版本

0.1.2 2023年1月11日
0.1.1 2022年12月25日
0.1.0 2022年12月25日

#1755 in 异步

MIT 许可证

26KB
400

Akt

Rust和Tokio的actor框架。

它深受Actix的启发,现在它与Actix非常相似。主要区别在于,在Actix中(至少在创建这个特定库的时候)处理async/await和异步处理器有点繁琐。这个库支持开箱即用的异步处理器,并提供工具来简化一些不寻常的通信方式,例如等待结果而不会阻塞主消息循环。

docs.rs Crates.io

状态

这个库还处于开发初期阶段。这意味着

  • 您可能会遇到大量错误
  • API可能会在任何时候发生不兼容的更改
  • 测试覆盖率不完整

然而,它已经在生产中使用,因此您也可以在您的项目中尝试使用它。

概述

让我们实现一些actor来管理银行账户。

use mpi_actors::{Actor, Context, Handler, Message};
use async_trait::async_trait;
use thiserror::Error;

// A state managed by our actor
struct Account {
    balance: u32,
}

// Implementing Actor trait makes an actor out of the state
impl Actor for Account {}

// Define messages to be handled by the actor

struct Deposit {
    pub amount: u32,
}

impl Message for Deposit {
  // Result the actor will respond with
    type Result = u32;
}

// Implement handler for the Deposit message and Account actor
// We need to use `async_trait` crate "magic" because async trait methods
// is not supported bu rust directly yet.
#[async_trait]
impl Handler<Deposit> for Account {
    async fn handle(&mut self, message: Deposit, _context: &mut Context<Account>) -> u32 {
        self.balance = self.balance + message.amount;

        self.balance
    }
}

struct Withdraw {
  pub amount: u32,
}

// Withdrawal may fail so we could provide specific error for it.
//
// It could be a struct, but for backwards compatibility reasons it could
// be a good idea to make enum even if for now we have only one variant.
//
// Here we use `thiserror` crate for convenient Error
// derivation, but it is completely optional.
#[derive(Debug, Error, PartialEq)]
enum WithdrawalError {
    #[error("Insufficient funds. {requested} was requested but only {available} is available.")]
    InsufficientFunds { requested: u32, available: u32 },
}

impl Message for Withdraw {
    type Result = Result<u32, WithdrawalError>;
}

#[async_trait]
impl Handler<Withdraw> for Account {
    async fn handle(
        &mut self,
        message: Withdraw,
        _context: &mut Context<Account>,
    ) -> Result<u32, WithdrawalError> {
        if self.balance < message.amount {
            return Err(WithdrawalError::InsufficientFunds {
                requested: message.amount,
                available: self.balance,
            });
        }

        self.balance = self.balance - message.amount;

        Ok(self.balance)
    }
}

#[tokio::main]
async fn main() {
   // Create actor
   let actor = Account { balance: 50 };

   // Run actor consuming it and returning its address
   let address = actor.run();

   // Send message and use regular `await` workflow to wait for the response.
   let balance = address.send(Withdraw { amount: 20 }).await;
   assert_eq!(balance, Ok(Ok(30)));

   let balance = address.send(Deposit { amount: 40 }).await;
   assert_eq!(balance, Ok(70));

   let balance = address.send(Withdraw { amount: 100 }).await;
   assert_eq!(
       balance,
       Ok(Err(WithdrawalError::InsufficientFunds {
           requested: 100,
           available: 70
       }))
   );

   // Actors addresses could be safely cloned
   let address_clone = address.clone();
   let balance = address_clone.send(Withdraw { amount: 10 }).await;
   assert_eq!(balance, Ok(Ok(60)));

   let balance = address.send(Withdraw { amount: 10 }).await;
   assert_eq!(balance, Ok(Ok(50)));
}

依赖项

~2.3–4MB
~65K SLoC