[][src]Crate thespian

Introduction

Thespian implements a basic actor system in async Rust inspired heavily by Erlang Processes and Supervisors.

Instead of providing a direct messaging interface to the Actor, methods defined by the package user on impl Actor<State, Reply> are used as the external API for the actor. This makes usage of an Actor no different than using a reference to a struct.

Internal to the Actor implementation a mechanism is provided to run a function inside the Process holding the Actor state. Since all access to the state is handled serially by the Process, the behavior of the system resembles a typical Actor system.

While technically the Process would be called the actor in typical usage of the term, for this package the actor's mailbox is instead called Actor for better ergonomics. Defining methods on a mailbox felt odd and confusing.

TODO

  • Fix ergonomics around use of newtype pattern for Actor
  • Implement supervision tree

Example usage

#[tokio::main]
pub async fn main() {
    let (mut process, actor) =
        thespian::Process::<State, State>::new_with_state(State::Alpha);
    let toggle = Toggle(actor);

    let (_process_result, _task_result) = tokio::join! {
        async move {
            process.start().await;
        },
        async move {
            toggle.flip();
            toggle.flip();
            println!("get: {:?}", toggle.get().await);
        }
    };
}

#[derive(Copy, Clone, Debug)]
enum State {
    Alpha,
    Beta,
}

struct Toggle(thespian::Actor<State, State>);

impl Toggle {
    pub async fn get(&self) -> State {
        self.0.call_ref_reply(|state, reply| {
            reply.send(state.clone());
        }).await
    }

    pub fn flip(&self) {
        self.0.call_ref_mut(|state| {
            println!("state: {:?}", state);
            match state {
                State::Alpha => *state = State::Beta,
                State::Beta => *state = State::Alpha,
            }
        });
    }

}

Structs

Actor

Actor maintains a connection to its Process to allow Actor methods to be implemented via functions sent to the Process.

Process

Process holds the Actor state and sequentially processes calls sent from the Actor.