black-box 0.3.1

A minimal actor framework
Documentation
use std::{future::Future, pin::Pin, sync::Arc};

use crate::{
    actors::{Actor, Address, Handler},
    message::Message,
};

trait MailboxSender<T: Message>: Send + Sync {
    fn send<'a>(&'a self, msg: T) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
    fn try_send(&self, msg: T);
}

impl<A, T> MailboxSender<T> for Address<A>
where
    A: 'static + Actor + Send + Handler<T>,
    T: Message,
{
    fn send<'a>(&'a self, msg: T) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
        Box::pin(Address::send(self, msg))
    }

    fn try_send(&self, msg: T) {
        Address::try_send(self, msg);
    }
}

/// A type-erased, cloneable handle for sending messages of type `T` to an actor.
///
/// Unlike [`Address<A>`], `Mailbox<T>` is generic over the message type rather than the actor
/// type. This allows passing a mailbox to code that only needs to send a specific message type
/// without exposing the concrete actor `A`.
///
/// Construct a `Mailbox<T>` from an [`Address<A>`] via [`Address::into_mailbox`].
pub struct Mailbox<T: Message> {
    sender: Arc<dyn MailboxSender<T>>,
}

impl<T: Message> Clone for Mailbox<T> {
    fn clone(&self) -> Self {
        Self {
            sender: Arc::clone(&self.sender),
        }
    }
}

impl<T: Message> std::fmt::Debug for Mailbox<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Mailbox").finish_non_exhaustive()
    }
}

impl<T: Message> Mailbox<T> {
    /// Send a message, awaiting if the channel is full.
    pub async fn send(&self, msg: T) {
        self.sender.send(msg).await
    }

    /// Try to send a message without awaiting. The message is dropped if the channel is full.
    pub fn try_send(&self, msg: T) {
        self.sender.try_send(msg);
    }
}

impl<A> Address<A>
where
    A: 'static + Actor + Send,
{
    /// Convert this address into a [`Mailbox<T>`], erasing the actor type.
    ///
    /// The resulting mailbox can send messages of type `T` to the actor without exposing `A`.
    pub fn into_mailbox<T>(self) -> Mailbox<T>
    where
        A: Handler<T>,
        T: Message,
    {
        Mailbox {
            sender: Arc::new(self),
        }
    }
}

impl<A, T> From<Address<A>> for Mailbox<T>
where
    A: 'static + Actor + Send + Handler<T>,
    T: Message,
{
    fn from(address: Address<A>) -> Self {
        address.into_mailbox()
    }
}

#[cfg(test)]
mod tests {
    use crate::{executor::Context, Executor};

    use super::*;

    struct Ping;
    struct Pong;

    struct MyActor;
    impl Actor for MyActor {}

    impl Handler<Ping> for MyActor {
        async fn handle(&mut self, _msg: Ping, _ctx: &Context<Self>) {}
    }

    impl Handler<Pong> for MyActor {
        async fn handle(&mut self, _msg: Pong, _ctx: &Context<Self>) {}
    }

    #[test]
    fn into_mailbox_compiles() {
        let (_executor, address) = Executor::new(MyActor);
        let _mailbox: Mailbox<Ping> = address.into_mailbox();
    }

    #[test]
    fn from_address_compiles() {
        let (_executor, address) = Executor::new(MyActor);
        let _mailbox: Mailbox<Pong> = Mailbox::from(address);
    }

    #[test]
    fn mailbox_is_clone() {
        let (_executor, address) = Executor::new(MyActor);
        let mailbox: Mailbox<Ping> = address.into_mailbox();
        let _ = mailbox.clone();
    }

    #[test]
    fn mailbox_is_debug() {
        let (_executor, address) = Executor::new(MyActor);
        let mailbox: Mailbox<Ping> = address.into_mailbox();
        let _ = format!("{:?}", mailbox);
    }
}