Skip to content

rtic-sync: DynSender and DynReceiver #62

@wiktorwieclaw

Description

@wiktorwieclaw

I'd like to have dynamically dispatched versions of Sender and Receiver with type erased queue size.

struct Sender<'a, T, const N: usize>(&'a Channel<T, N>);

// no queue size in the type!
struct DynSender<'a, T>(&'a dyn ChannelImpl<T>);

Similar feature is already implemented in embassy_sync.

Motivation

I'm trying to implement an actor model around RTIC. Each actor is supposed to have an address that can be used to send messages to it.

struct Addr<A: Actor, const N: usize> {
    sender: Sender<'static, A::Msg, N>
}

Having the channel's size embedded in the Address type is inconvenient and makes some patterns impossible.

Implementation

trait ChannelImpl<T> {
    fn send_footer(&mut self, idx: u8, val: T);
    fn try_send(&mut self, val: T) -> Result<(), TrySendError<T>>;
    // oh no! #![feature(async_fn_in_trait)] required, more on that later...
    async fn send(&mut self, val: T) -> Result<(), NoReceiver<T>>;
    fn try_recv(&mut self) -> Result<T, ReceiveError>;
    async fn recv(&mut self) -> Result<T, ReceiveError>;
    fn is_closed(&self) -> bool;
    fn is_full(&self) -> bool;
    fn is_empty(&self) -> bool;
    fn handle_sender_clone(&mut self);
    fn handle_receiver_clone(&mut self);
    fn handle_sender_drop(&mut self);
    fn handle_receiver_drop(&mut self);
}

impl<T, const N: usize> ChannelImpl<T> for Channel<T, N> {
     // -- snip! --
}

struct Sender<'a, T, const N: usize>(&'a Channel<T, N>);

impl<'a T, const N: usize> Sender<'a, T, N> {
        fn into_dyn(self) -> DynSender<'a, T> {
            let sender = DynSender(self.0);
            core::mem::forget(self);
            sender
        }

        #[inline(always)]
        fn try_send(&mut self, val: T) -> Result<(), TrySendError<T>> {
             // forward
             self.0.try_send(val)
        }
        
        // forward implementation for the other methods
        // -- snip! --
}

struct DynSender<'a, T>(&'a dyn ChannelImpl<T>);

impl<'a T> DynSender<'a, T> {
        #[inline(always)]
        fn try_send(&mut self, val: T) -> Result<(), TrySendError<T>> {
             // forward
             self.0.try_send(val)
        }
        
        // forward implementation for the other methods
        // -- snip! --
}

Of course, we can't have async functions in traits, but we could work around that by manually implementing SendFuture and RecvFuture types and returning them from regular functions.

fn send(&mut self, val: T) -> SendFuture;

I'd be happy to write a PR if I get a green light, I may want to ask for some help implementing the futures as I've never done this before.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions