Expand description
§Meslin
Meslin is a Rust library offering ergonomic wrappers for channels like mpmc
and broadcast. It’s designed to ease the creation of actor systems by adding
user-friendly features, without being tied to any specific runtime. This makes
it compatible with various runtimes such as tokio, smol, or async-std.It intentionally
steers clear of incorporating supervisory functions or other complex features, focusing
instead on simplicity and non-interference.
Meslin is designed with a zero-cost abstraction principle in mind, ensuring that its ease of use and flexibility don’t compromise performance. When not using any dynamic features of the library, Meslin does not add any additional runtime overhead compared to hand-written equivalents.
§Concepts
§Messages
All messages that are sent through a channel must implement the Message trait.
The trait defines two associated types: Message::Input and Message::Output.
When sending a message to an actor, you only need to provide the input type and if the message
is sent succesfully, the output type is returned.
Message is implemented for a lot of common types, like i32, String, Vec<T>, etc.
Furthermore, it is implemented for Msg<M> and Request<A, B>. The first is a simple
wrapper that allows sending any type that does not implement Message. The second is a
message that requires a response, i.e. the output is actually a oneshot::Receiver.
The Message derive-macro can be used to derive Message for custom types.
§Protocols
Protocols define the messages that can be received by an actor. For every message M that
can be received, the protocol must implement From<M> and TryInto<M>. These traits can
be derived using the From and TryInto derive-macros.
Optionally, the protocol can implement DynFromInto and AsSet using the derive-macro DynFromInto.
This allows for conversion of senders into dynamic senders. See DynSender for more information.
§Senders
Senders are responsible for defining the delivery mechanism of a protocol. They implement
IsSender and can be used to send messages using Sends<M>. Examples of some default
senders are mpmc::Sender, priority::Sender and the DynSender.
Most senders have their associated type IsSender::With set to (), meaning that they
don’t require any additional data to send a message. However, some senders, like
priority::Sender do require additional data.
§Send methods
The SendsExt and DynSendsExt traits provide a bunch of methods for sending messages.
The following are the modifier keywords and their meaning:
send: The base method, that asynchronously sends a message and waits for space to become available.request: After sending the message, theMessage::Outputis awaited and returned immeadeately.{...}_with: Instead of using the defaultIsSender::Withvalue, a custom value is given.try_{...}: Sends a message, returning an error if space is not available.{...}_blocking: Sends a message, blocking the current thread until space becomes available.{...}_msg: Instead of giving theMessage::Input, the message itself is given.dyn_{...}: Attempts to send a message, when it can not be statically verified that the actor will accept the message.
§Dynamic senders
A unique feature of Meslin is the transformation of senders into dynamic senders,
converting any sender into a dyn DynSends<W>. This allows for storage
of different sender types in the same data structure, like Vec<T>.
DynSender provides an abstraction over a Box<dyn DynSends>, allowing for
type-checked dynamic dispatch and conversions. For example,
if you have an mpmc::Sender<ProtocolA> and a broadcast::Sender<ProtocolB>,
both accepting messages Msg1 and Msg2, they can both be converted into
DynSender<Set![Msg1, Msg2]>. This dynamic sender then implements
Sends<Msg1> + Sends<Msg2>.
The Set macro can be used to define the accepted messages of a dynamic sender. Some
examples of dynamic sender conversions:
Set![Msg1, Msg2]==dyn Two<Msg1, Msg2>.DynSender<Set![Msg1, Msg2]>can be converted intoDynSender<Set![Msg1]>.mpmc::Sender<ProtocolA>can be converted intoDynSender<Set![Msg1, ...]>as long asProtocolAimplementsDynFromIntoandContains<Msg1> + Contains<...> + ....
§Cargo features
The following features are available:
- Default features:
["derive", "request", "mpmc", "broadcast", "priority"] - Additional features: `[“watch”]“”
§Basic example
use meslin::{mpmc, From, Message, Request, SendsExt, TryInto};
// Create a simple, custom message type
#[derive(Debug, From, Message)]
#[from(forward)]
struct MyMessage(String);
// Create the protocol used by the actor
// It defines the messages that can be sent
#[derive(Debug, From, TryInto)]
enum MyProtocol {
Number(i32),
Message(MyMessage),
Request(Request<i32, String>),
}
#[tokio::main]
async fn main() {
// Create the channel and spawn a task that receives messages
let (sender, receiver) = mpmc::unbounded::<MyProtocol>();
tokio::task::spawn(receive_messages(receiver));
// Send a number
sender.send::<i32>(42).await.unwrap();
// Send a message
sender.send::<MyMessage>("Hello").await.unwrap();
// Send a request and then wait for the reply (oneshot channel)
let rx = sender.send::<Request<i32, String>>(42).await.unwrap();
let reply = rx.await.unwrap();
assert_eq!(reply, "The number is 42");
// Send a request and receive the reply immeadiately
let reply = sender.request::<Request<i32, String>>(42).await.unwrap();
assert_eq!(reply, "The number is 42");
}
// This is completely standard: `mpmc::Receiver` == `flume::Receiver`
async fn receive_messages(receiver: mpmc::Receiver<MyProtocol>) {
while let Ok(msg) = receiver.recv_async().await {
match msg {
MyProtocol::Number(msg) => {
println!("Received number: {msg:?}");
}
MyProtocol::Message(msg) => {
println!("Received message: {msg:?}");
}
MyProtocol::Request(Request { msg, tx }) => {
println!("Received request: {msg:?}");
tx.send(format!("The number is {}", msg)).ok();
}
}
}
}§Advanced example
use meslin::{
mpmc, priority, DynFromInto, DynSender, DynSendsExt, From, MappedWithSender, SendsExt, TryInto,
WithValueSender,
};
#[derive(Debug, From, TryInto, DynFromInto)]
enum P1 {
A(i32),
B(i64),
C(i128),
}
#[derive(Debug, From, TryInto, DynFromInto)]
enum P2 {
A(i16),
B(i32),
C(i64),
}
#[tokio::main]
async fn main() {
// Create two different senders, sending different protocols
let (sender1, _receiver) = mpmc::unbounded::<P1>(); // Sends `P1` with `()`
let (sender2, _receiver) = priority::unbounded::<P2, u32>(); // Sends `P2` with `u32` as priority
// Sending messages to the senders:
sender1.send::<i32>(8).await.unwrap(); // Normal
sender2.send::<i32>(8).await.unwrap(); // Uses `u32::default()` as priority
sender2.send_with::<i32>(8, 15).await.unwrap(); // Uses `15` as priority
// Create a vector of dynamic senders: (Checked at compile time)
let senders: Vec<DynSender![i32, i64]> = vec![
// For sender1, use `into_dyn` to transform it into a DynSender
sender1.into_dyn(),
// For sender2, use `with` / `map_with` and then `into_dyn` to transform it into a DynSender
// This sender will always send `15` as the priority
sender2.clone().with(15).into_dyn(),
sender2.map_with(|_| 15, |_| ()).into_dyn(),
];
// Send a `i32` or `i64` to the senders
senders[0].send::<i32>(8).await.unwrap();
senders[1].send::<i64>(8).await.unwrap();
senders[2].send::<i32>(8).await.unwrap();
// Downcast the senders back to their original types
let _sender1 = senders[0].downcast_ref::<mpmc::Sender<P1>>().unwrap();
let _sender2 = senders[1]
.downcast_ref::<WithValueSender<priority::Sender<P2, u32>>>()
.unwrap();
let _sender3 = senders[2]
.downcast_ref::<MappedWithSender<priority::Sender<P2, u32>, ()>>()
.unwrap();
}Modules§
- broadcast
- A broadcast channel using
async_broadcast. - mpmc
- An mpmc channel using
flume. - oneshot
- A oneshot channel using
oneshot. - priority
- A priority channel using
async_priority_channel. - type_
sets - Re-export of
type_sets. - watch
- A watch channel using
tokio::sync::watch.
Macros§
Structs§
- Boxed
Msg - A boxed message with a
withvalue, used for dynamic dispatch. - DynSender
- A wrapper around a
Box<dyn DynSends>that allows for type-checked conversions. - Mapped
With Sender - A wrapper around a sender, which provides a mapping between the
with-value of the sender and a customwith-value. - Msg
- A simple wrapper for any type that does not implement
Message. - Request
- A
Messagewith inputA, returning a responseB. - Send
Error - Error that is returned when a channel is closed.
- Set
- Marker struct to act as a set of elements.
- With
Value Sender - A wrapper around a sender, which provides a default
with-value.
Enums§
- DynSend
Error - Error that is returned when a channel is closed, or the message was not accepted.
- DynTry
Send Error - Error that is returned when a channel is closed, full, or the message was not accepted.
- Request
Error - Error that is returned when a channel is full, or the request did nor receive a reply
- TrySend
Error - Error that is returned when a channel is closed or full.
Traits§
- DynFrom
Into - Trait that allows usage of dynamic senders for a protocol
- DynSends
- Automatically implemented when
SendsProtocolis implemented for a protocol that implementsDynFromInto. - DynSends
Ext - Extension trait for
DynSends, providing methods for dynamic dispatch. - IsSender
- Trait that must be implemented by all senders.
- Message
- Trait that defines how a message is created and canceled.
- Result
Future - A future that resolves to a
Result. - Sends
- Defines when a message
Mcan be sent to the sender. - Sends
Ext - Extension methods for
IsSender/Sends<M>. - Sends
Protocol - A supertrait of
IsSender, that defines how a protocol can be sent to the sender.
Derive Macros§
- DynFrom
Into - Macro to derive
DynFromIntoandAsSetfor an enum. - From
- Re-export of
derive_more::From. - Message
- Macro to derive
Messagefor a type. - TryInto
- Re-export of
derive_more::TryInto.