actor-helper
A minimal, opinionated actor framework for sync and async Rust.
Features
- Runtime agnostic: Works with
tokio,async-std, or blocking threads - Dynamic error types: Use
io::Error,anyhow::Error,String, or custom types - Simple API: Create actors with just a struct and a handle
- Type-safe: Compile-time guarantees for actor interactions
- Panic-safe: Automatic panic capture and error propagation with location tracking
- Ergonomic macros:
act!andact_ok!for writing actor actions - Thread-safe: Clone handles to communicate from anywhere
Direct Actor Access
actor-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.await?;
// actor-helper approach - direct function execution:
handle.call.await?;
This design offers several advantages:
- No message types: Write functions directly instead of defining enums/structs
- Type safety: Full compile-time checking of actor interactions
- Flexibility: Execute any logic on the actor state, including async operations (be careful to keep them fast!)
- Simplicity: Less boilerplate, more readable code
The Handle is cloneable 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.
Important: Keep Actions Fast
Actions run sequentially and should complete quickly. A slow action blocks the entire actor:
// DON'T: Long-running work blocks the actor
pub async
// DO: Get state, process outside, write back
pub async
// DO: Quick mutations inside
pub async
// DO: Use spawn_with for background tasks alongside actions
let = spawn_with;
Quick Start
Add to your Cargo.toml:
[]
= { = "0.3", = ["tokio"] }
= { = "1", = ["rt-multi-thread"] }
Example with tokio
use io;
use ;
// Public API
// Private actor implementation
pub async
Blocking/Sync Example
No async runtime required:
Add to your Cargo.toml:
[]
= { = "0.3" }
use io;
use ;
// Public API
// Private actor implementation
Using Custom Error Types
With anyhow::Error
Enable the feature:
[]
= { = "0.3", = ["anyhow", "tokio"] }
= "1"
Then use it in your code:
use ;
use ;
Custom Error Type
Implement the ActorError trait:
use ActorError;
// Now use Handle<MyActor, MyError>
async-std Support
[]
= { = "0.3", = ["async-std"] }
= { = "1", = ["attributes"] }
The API is identical to tokio, just use #[async_std::main] instead.
How It Works
- Spawn the actor:
Handle::spawn(actor)orHandle::spawn_blocking(actor)creates the channel and starts the message loop - Call actions: Use
handle.call()orhandle.call_blocking()withact!oract_ok!macros - Sequential execution: Actions are processed one at a time by the actor
- Panic safety: Panics are caught and converted to errors with the call site location
License
MIT