[−][src]Crate ghost_actor
GhostActor makes it simple, ergonomic, and idiomatic to implement async / concurrent code using an Actor model.
GhostActor uses only safe code, and is futures executor agnostic--use tokio, futures, async-std, whatever you want. The following examples use tokio.
What does it do?
The GhostActor struct is a 'static + Send + Sync
cheaply clone-able
handle for managing rapid, efficient, sequential, mutable access to
internal state data.
Using the raw type:
// set our initial state let (a, driver) = GhostActor::new(42_u32); // spawn the driver--using tokio here as an example tokio::task::spawn(driver); // invoke some logic on the internal state (just reading here) let result: Result<u32, GhostError> = a.invoke(|a| Ok(*a)).await; // assert the result assert_eq!(42, result.unwrap());
Best Practice: Internal state in a New Type:
GhostActor is easiest to work with when you have an internal state struct, wrapped in a new type of a GhostActor:
struct InnerState { age: u32, name: String, } #[derive(Clone, PartialEq, Eq, Hash)] pub struct Person(GhostActor<InnerState>); impl Person { pub fn new(age: u32, name: String) -> Self { let (actor, driver) = GhostActor::new(InnerState { age, name }); tokio::task::spawn(driver); Self(actor) } pub async fn birthday(&self) -> String { self.0.invoke(|inner| { inner.age += 1; let msg = format!( "Happy birthday {}, you are {} years old.", inner.name, inner.age, ); <Result::<String, GhostError>>::Ok(msg) }).await.unwrap() } } let bob = Person::new(42, "Bob".to_string()); assert_eq!( "Happy birthday Bob, you are 43 years old.", &bob.birthday().await, );
Using traits (and GhostFuture) to provide dynamic actor types:
pub trait Fruit { // until async traits are available in rust, you can use GhostFuture fn eat(&self) -> GhostFuture<String, GhostError>; // allows implementing clone on BoxFruit fn box_clone(&self) -> BoxFruit; } pub type BoxFruit = Box<dyn Fruit>; impl Clone for BoxFruit { fn clone(&self) -> Self { self.box_clone() } } #[derive(Clone, PartialEq, Eq, Hash)] pub struct Banana(GhostActor<u32>); impl Banana { pub fn new() -> BoxFruit { let (actor, driver) = GhostActor::new(0); tokio::task::spawn(driver); Box::new(Self(actor)) } } impl Fruit for Banana { fn eat(&self) -> GhostFuture<String, GhostError> { let fut = self.0.invoke(|count| { *count += 1; <Result<u32, GhostError>>::Ok(*count) }); // 'resp()' is a helper function that builds a GhostFuture // from any other future that has a matching Output. resp(async move { Ok(format!("ate {} bananas", fut.await.unwrap())) }) } fn box_clone(&self) -> BoxFruit { Box::new(self.clone()) } } // we could implement a similar 'Apple' struct // that could be interchanged here: let fruit: BoxFruit = Banana::new(); assert_eq!("ate 1 bananas", &fruit.eat().await.unwrap());
Custom GhostActor error types:
The GhostActor::invoke()
function takes a generic error type.
The only requirement is that it must implement From<GhostError>
:
#[derive(Debug)] struct MyError; impl std::error::Error for MyError {} impl std::fmt::Display for MyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } impl From<GhostError> for MyError { fn from(_: GhostError) -> Self { Self } } let (actor, driver) = GhostActor::new(42_u32); tokio::task::spawn(driver); assert_eq!(42, actor.invoke(|inner| { <Result<u32, MyError>>::Ok(*inner) }).await.unwrap());
Code Examples:
- Bounce:
cargo run --example bounce
Contributing:
This repo uses cargo-task
.
cargo install cargo-task cargo task
Modules
dependencies | Re-exported dependencies. |
ghost_actor_trait | Background utilities for dealing with the |
Macros
ghost_box_new_type | Ghost box helper macro - new type variant. Place this outside your new type definition. |
ghost_box_trait | Ghost box helper macro - trait variant. Place this outside your trait definition. |
ghost_box_trait_fns | Ghost box helper macro - trait fn variant. Place this inside your trait definition. |
ghost_box_trait_impl_fns | Ghost box helper macro - trait impl fn variant. Place this inside your impl trait definition. |
Structs
BoxGhostActor | Newtype wrapping boxed type-erased trait-object version of GhostActor.
Prefer using the strongly typed |
GhostActor | GhostActor manages task efficient sequential mutable access
to internal state data (type T).
GhostActors are |
GhostConfig | Configuration tuning parameters for GhostActors |
GhostDriver | Driver future representing an actor task. Please spawn this into whatever executor framework you are using. |
GhostError | Generic GhostActor Error Type |
GhostFuture | Result future for GhostActor#invoke(). |
Functions
resp | Wrap another compatible future in an GhostFuture. |