What is your dream channel API?

I just watched the async interview between @nikomatsakis and @withoutboats. Thank you, very interesting!

@withoutboats kind of makes a call out for someone to write an RFC for a new channel API for std. Unfortunately, I'm not in a position where I can pretend I'm going to write an RFC, at least not now, but that doesn't keep me from dreaming :star_struck:

So I just thought I throw my dream in the middle here in the hopes of setting of some discussion that might even get us somewhere if we are lucky...

Disclaimer I haven't done any research on like the state of the art of channels in computer science or something so I might be missing good ideas here. But this covers most of what's available in Rust today I think and the API is simple, not to say tiny, which I think is a great feature...

My dream channel API:

let (sender: Sender, receiver: Receiver) = channel::<T>( config: ChannelConfig<T> );

where

// Both have some method that will block for sync use.
// 
Sender  : Debug + PartialEq + Eq + Unpin + Clone + Sink<T, Error=ChannelError> + ...  + Send where T: Send, etc
Receiver: Debug + PartialEq + Eq + Unpin + Clone + Stream< Result< Option<T>, ChannelError > > + ...  + Send where T: Send, etc


// Implements Default + Debug + Clone + you get it...
// 
struct ChannelConfig<T>
{
   // with 0 being unbounded. I don't quite get the every sender get's
   // a guaranteed slot. I find it just confusing... but maybe there's
   // a good reason for it. Less is more!
   // 
   backpressure: usize, // or Option< NonZeroUsize >

   // with 1 being a oneshot. 0 is no limit. Maybe this is redundant. If you want
   // oneshot, just drop the Receiver after receiving one message.
   // 
   limit: usize, // or Option< NonZeroUsize >

   // when hitting the backpressure limit, overwrite older messages 
   // rather than providing back pressure. Hint: use a ringbuffer internally?
   // 
   drop: bool, 

   // Deliver the message to all Receivers, or just to one?
   // Can only work if T: Clone. The API should somehow enforce this...
   // 
   broadcast: bool, 

   // maybe something I forget...
}

So I think we're not at all in a place of getting something in std right now, but if there was a crate out there with such an API and we would collectively be confident that it performs best in class for all different use cases, it could go in std.

You might feel this tries to pack a bunch of features in one thing. That's true, and that's ambitious. But keeping the type soup down is really a thing. I can give one example. I write an actor library which uses a channel between the address and the mailbox. I don't really want to make any decision about the ChannelConfig here.

But today, what type do I take in the constructors of Addr and Mailbox? I can't take Box< dyn Sink + Clone >. So I now have to make my Addr<A: Actor, S: Sink + Clone> generic over the Sender type? :face_vomiting: That's not going to happen.

So I need to start creating abstractions to allow the user to pass in channels from tokio, futures, async-std, futures-intrusive, ring-channel, and whatever the best channel will be tomorrow. How nice would it be if there where just one type.

An alternative, which keeps the competition open would be to have some sort of interface where crates could provide the implementation, so the user can choose their implementation, but I think I would prefer something that comes batteries included and is good enough that most people are happy using it.

1 Like

I just found a solution to the Sink + Clone interface. I just created an extension trait with a blanket impl:

use crate::import::*;

/// Interface for T: Sink + Clone
//
pub trait CloneSink<'a, Item, E>: Sink<Item, Error=E> + Unpin + Send + Sync
{
   /// Clone this sink.
   //
   fn clone_sink( &self ) -> Box< dyn CloneSink<'a, Item, E> + 'a >;
}


impl<'a, T, Item, E> CloneSink<'a, Item, E> for T

   where T: 'a + Sink<Item, Error=E> + Clone + Unpin + Send + Sync + ?Sized

{
   fn clone_sink( &self ) -> Box< dyn CloneSink<'a, Item, E> + 'a >
   {
      Box::new( self.clone() )
   }
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.