Expand description
Β§priact
A lightweight, priority-driven actor framework for Rust.
Β§π¦ priact π¦
A lightweight and ergonomic Actor implementation for Rust, built on tokio, featuring explicit message prioritization via a BinaryHeap. This is based on the Actor concept from Swift.
Β§β¨ Features
- Actor Model Implementation: Provides a robust foundation for building concurrent, stateful components that safely manage mutable data.
- Data Race Prevention: Ensures serial processing of messages, eliminating data races on the actorβs internal state.
- Asynchronous Messaging: Leverages
tokio::mpscchannels for efficient, non-blocking communication with actors. - Message Prioritization: Messages can be assigned
Low,Medium(the default), orHighpriority, allowing critical operations to be processed ahead of others. - Ergonomic
define_actor!Macro: Simplifies actor definition by automatically generating message enums andhandlelogic, reducing boilerplate. - Built on
tokio: Seamlessly integrates with thetokioasynchronous runtime.
Β§π‘ Why priact?
While Rustβs ownership system makes data races less common, managing mutable state across asynchronous tasks can still be challenging. The Actor Model provides a clear pattern for this, and priact offers:
- Simplicity: A focused, opinionated implementation of the core Actor Model, without the overhead of a full framework if you only need the actor primitive.
- Safety: Guarantees that your actorβs internal state is accessed by only one task at a time.
- Control: The unique message prioritization feature gives you fine-grained control over message processing order, crucial for real-time systems, performance-sensitive applications, or managing resource contention.
- Developer Experience: The
define_actor!macro makes defining actors straightforward and enjoyable.
Β§π Getting Started
Add priact to your Cargo.toml:
[dependencies]
priact = "0.1.0" # Check crates.io for the latest version
tokio = { version = "1", features = ["full"] } # Or specific features you need
async-trait = "0.1" # Required by the Actor traitΒ§Basic Usage
Define your actor and its messages using the define_actor! macro:
use priact::{define_actor, spawn_actor, Actor, Priority};
use tokio::sync::oneshot;
// Define your actor's state and its methods
define_actor! {
/// A simple counter actor.
TestCounter {
count: i32,
}
// Define the messages your actor can handle
impl TestCounterMsg {
// Methods prefixed with @priority will be translated into messages
// `self` here refers to the actor's internal state
// High-priority βreadβ message
@priority(High)
fn GetValue(&mut self, tx: oneshot::Sender<i32>) {
let _ = tx.send(self.count);
}
// Low-priority βwriteβ message
@priority(Low)
fn Increment(&mut self, ack: oneshot::Sender<()>) {
self.count += 1;
let _ = ack.send(());
}
// Medium-priority asynchronous decrement
@priority(Medium)
async fn DecrementAsync(&mut self) {
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
self.count -= 1;
}
}
}
#[tokio::main]
async fn main() {
// Create actor state
let counter = TestCounter { count: 0 };
// Spawn it, getting back a `mpsc::Sender<TestCounterMsg>`
let tx = spawn_actor(counter);
// Send some messages...
let (ack_tx, ack_rx) = oneshot::channel();
tx.send(TestCounterMsg::Increment(ack_tx)).await.unwrap();
ack_rx.await.unwrap();
// Query the current count
let (resp_tx, resp_rx) = oneshot::channel();
tx.send(TestCounterMsg::GetValue(resp_tx)).await.unwrap();
let value = resp_rx.await.unwrap();
println!("Current count = {}", value);
// Shut down the actor
tx.send(TestCounterMsg::Shutdown).await.unwrap();
}Β§π Under the Hood
- mpsc Receiver Task
Listens on a Tokio mpsc channel and pushes messages into aBinaryHeap<PrioritizedWrapper>. - Processor Task
Pops highest-priority message, calls your typedhandleon the actor, and repeats. - Shutdown
- Explicit: A
Shutdownvariant returnsfalsefromhandle, tearing down both tasks. - Implicit: Dropping all
Senderhandles drains the queue then stops.
- Explicit: A
Β§π API Reference
define_actor!: Macro for defining actors and their messages.spawn_actor<A>(actor: A) -> mpsc::Sender<A::Msg>: Spawns an actor into atokiotask and returns a sender for its messages.Actortrait: Defines the behavior of an actor, requiring aMsgtype and ahandlemethod.Prioritizedtrait: Needs to be implemented by your message enum to specify priority.Priorityenum:Low,Medium,High.
For detailed API documentation, please refer to docs.rs.
Β§π€ Contributing
Thank you to @lucretiel and @ipetkov for help with the macro!
Contributions are welcome! Feel free to open issues or submit pull requests.
Β§π License
This project is licensed under the MIT License.
MacrosΒ§
- define_
actor - The procedural macro entry point