#message #subscriber #queue #typed #mechanism #dispatcher #greeting

bottles

An enum-less typed message passing mechanism

2 releases

0.1.1 Mar 24, 2020
0.1.0 Mar 24, 2020

#8 in #greeting

MIT license

16KB
220 lines

Bottles

A Rust library for strongly typed message passing without the use of sum types. The benefit of this approach is that new message types are very easy to add the size of each is not the largest of all (as it is in case of enums).

Status

This is strongly WIP.


lib.rs:

bottles

An enum-less typed message passing mechanism

This crate allows to put a message in form of any 'static type in a bottles, which then can be dispatched to subscribers of that type.

There are 2 main types in this crate: Dispatcher and Queue

Dispatcher

A simple mapping between a types and a list of subscribers to a message of that type. The subscriber is a closure which takes a single argument of type Rc<T> which will be called whenever a message with that type is dispatched.

Most often it is cumbersome to react to messages without any context, one would have to rely on Rc<RefCell<Context>> capturing in the closure in order to mutate the outside world. The easiest solution provided is to use a Queue to act as an intermediate component between the Dispatcher and the callback itself.

Queue

Queue allows one to collect messages from one or several dispatchers and queue them internally to be dispatched on a call to the Queue.poll method which allows for providing a "context" value to all subscribers.

The Queue subscriber is a closure taking exactly 2 arguments: a &mut reference to the context variable and a Rc<T> for message of type T

Examples

Send a greeting to a subscriber

use std::rc::Rc;
use bottles::Dispatcher;

struct Greeting {
    greeting: String
}

fn callback(msg: Rc<Greeting>) {
    println!("Greeting: {}", msg.greeting);
}

    let mut dispatcher = Dispatcher::new();

    // Every message type has to be registered explicitly
    // It is a design choice to have a error when subscribing for a message type which is
    // not registered by the particular Dispatcher, to error out as quickly as possible
    dispatcher.register::<Greeting>();

    dispatcher.subscribe(callback);

    dispatcher.dispatch(Rc::new(Greeting { greeting: "Hello there!".to_string() }));

Create a callback which has a mutable context

use std::rc::Rc;
use bottles::{Dispatcher, Queue};

struct Add(i32);

struct Context {
    state: i32
}

fn add(ctx: &mut Context, msg: Rc<Add>) {
    ctx.state += msg.0;
}

let mut dispatcher = Dispatcher::new();
let mut queue = Queue::new();

queue.register::<Add>(&mut dispatcher);

queue.subscribe(&mut dispatcher, add);

// `queue` will receive the message and enqueue it for retrieval when polled.
dispatcher.dispatch(Rc::new(Add(42)));

let mut context = Context {
    state: 0
};

// Very important: Queue has works by being polled, because this allows to pass a reference
// to the context without resorting to any interior mutability patterns.
queue.poll(&mut context);

assert_eq!(context.state, 42);

No runtime deps