lwactors

Crates.iolwactors
lib.rslwactors
version0.2.0
sourcesrc
created_at2018-02-23 18:10:02.023062
updated_at2020-12-28 16:42:04.154307
descriptionLightweight actors for Rust using futures-rs
homepage
repositoryhttps://github.com/benashford/lwactors
max_upload_size
id52560
size37,210
Ben Ashford (benashford)

documentation

README

lwactors

Build Status

Lightweight actors for Rust using futures-rs.

Introduction

The TL;DR is that you probably want to use Actix instead.

This library allows standalone "actors" to exist in Applications using Futures.

An actor is created with an initial state. From there the state can only be queried or modified by passing messages using the invoke function, receiving a future which will contain a result once the message has been processed. The messages are processed asynchronously.

See the "counter" example for an suggestion of how to build actors around this library.

Overview

The actor itself

An application would contain a bespoke struct for each actor, which would own a reference to the underlying future:

#[derive(Clone)]
struct Counter {
    queue: ActorSender<CounterAction, i64, CounterError>,
}

When constructed lwactors actor function is called to start the actor running:

fn new(cpu_pool: &CpuPool) -> Counter {
    Counter {
        queue: actor(cpu_pool, 0),
    }
}

it requires a reference to a CpuPool (or another Executor) and an initial state, in this case 0.

Action - the set of possible messages

The application will then define the set of messages that can be sent to the actor:

#[derive(Debug)]
enum CounterAction {
    Add(i64),
    Subtract(i64),
}

and the code that the actor will run when processing the message:

impl Action<i64, i64, CounterError> for CounterAction {
    fn act(self, state: &mut i64) -> Result<i64, CounterError> {
        println!("Acting {:?} on {}", self, state);
        match self {
            CounterAction::Add(i) => *state += i,
            CounterAction::Subtract(i) => *state -= i,
        }
        return Ok(*state);
    }
}

Usage

Finally, to expose a friendly high-level API, the application can implement specific functions which are responsible for sending messages to the actor:

impl Counter {
    // new function quoted above goes here

    fn add(&self, n: i64) -> CounterFuture {
        self.queue.invoke(CounterAction::Add(n))
    }

    fn subtract(&self, n: i64) -> CounterFuture {
        self.queue.invoke(CounterAction::Subtract(n))
    }
}

The struct that contains the reference to the actor Counter is cheaply cloneable, but all refer to the same running future that processes the messages, so all indirectly refer to the same shared state. Each thread that owns a Counter can call add or subtract to send messages, receiving a future that resolves to an i64 which is the state of the counter after that message has been processed.

The running future that processes messages will complete successfully once all the Counters have been dropped.

Commit count: 36

cargo fmt