| Crates.io | actor-helper |
| lib.rs | actor-helper |
| version | 0.1.1 |
| created_at | 2025-09-17 19:46:38.223667+00 |
| updated_at | 2025-09-17 19:51:10.059503+00 |
| description | Helper library for building actor systems |
| homepage | https://rustonbsd.github.io/ |
| repository | https://github.com/rustonbsd/actor-helper |
| max_upload_size | |
| id | 1843804 |
| size | 38,494 |
A minimal, ergonomic actor runtime built on top of Tokio.
act! and act_ok! for writing actor actionsactor-helper provides direct mutable access to actor state through closures. Instead of defining message types and handlers, you write functions that directly manipulate the actor:
// Traditional message passing approach:
// actor.send(Increment(5)).await?;
// actor-helper approach - direct function execution:
handle.call(act_ok!(actor => { actor.value += 5; })).await?;
This design offers several advantages:
The Handle is clonable and can be shared across threads, but all access to the actor's mutable state is serialized through the actor's mailbox, maintaining single-threaded safety.
Actions passed to handle.call() should complete quickly. The actor processes actions sequentially, so a slow action blocks the entire mailbox:
// ❌ DON'T: Long-running operations block the actor
handle.call(act!(actor => async move {
tokio::time::sleep(Duration::from_secs(10)).await; // Blocks other actions!
Ok(())
})).await?;
// ✅ DO: Spawn long-running work separately
handle.call(act_ok!(actor => {
let data = actor.get_work_data();
tokio::spawn(async move {
// Long operation runs independently
process_data(data).await;
});
})).await?;
For background work or continuous tasks, implement them in the actor's run() method using tokio::select!.
Add to your Cargo.toml:
[dependencies]
actor-helper = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "sync"] }
anyhow = "1"
use actor_helper::{Actor, Handle, act_ok, act};
use anyhow::{anyhow, Result};
use tokio::sync::mpsc;
// Public API
pub struct Counter {
handle: Handle<CounterActor>,
}
impl Counter {
pub fn new() -> Self {
let (handle, rx) = Handle::channel(128);
let actor = CounterActor { value: 0, rx };
tokio::spawn(async move {
let mut actor = actor;
let _ = actor.run().await;
});
Self { handle }
}
pub async fn increment(&self, by: i32) -> Result<()> {
self.handle.call(act_ok!(actor => async move {
actor.value += by;
})).await
}
pub async fn get(&self) -> Result<i32> {
self.handle.call(act_ok!(actor => async move {
actor.value
})).await
}
pub async fn set_positive(&self, value: i32) -> Result<()> {
self.handle.call(act!(actor => async move {
if value <= 0 {
Err(anyhow!("Value must be positive"))
} else {
actor.value = value;
Ok(())
}
})).await
}
}
// Private actor implementation
struct CounterActor {
value: i32,
rx: mpsc::Receiver<actor_helper::Action<CounterActor>>,
}
impl Actor for CounterActor {
async fn run(&mut self) -> Result<()> {
loop {
tokio::select! {
Some(action) = self.rx.recv() => {
action(self).await;
}
// Your background reader.recv() etc here!
}
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
let counter = Counter::new();
counter.increment(5).await?;
println!("Value: {}", counter.get().await?);
counter.set_positive(10).await?;
println!("Value: {}", counter.get().await?);
Ok(())
}
MIT