#proxy #tokio #send #msg

nightly no-std ctrlgen

Generate enums for message-passing services

7 releases

0.3.2 Sep 6, 2023
0.3.1 Nov 25, 2022
0.2.4 Nov 20, 2022
0.2.3 Oct 21, 2022
0.2.1 Sep 29, 2022

#293 in Asynchronous

MIT license

383 lines


A fork of vi/trait-enumizer that attempts to be easier to use, while sacrificing a bit of flexibility.

Documentation to come. See docs of trait-enumizer, and examples here for now.

Differences to trait-enumizer:

  • ctrlgen only supports inherent impls, it will not enumize traits.
  • ctrlgen prefers generating impls of traits over inherent functions, to make it more transparent to the user what is done. For example, the call function is implemented by implementing the CallMut trait on the enum.
  • ctrlgen tries to minimize and simplify the argument syntax, at the cost of some configurability. For example, the call trait will always be implemented.
  • ctrlgen requires the nightly rust channel for (amongst other things) GATs
  • ctrlgen supports generics in the struct definition (but not in the method signatures)
  • Proxies are implemented slightly differently, and are generally simpler. However, they currently don't support a lot of the options trait-enumizer has.
  • Proxies with async senders are currently not implemented
  • Async return value senders are currently not implemented.
  • no_std support is untested/unimplemented, but will be easy to do.

Most of the efforts here could probably be merged into trait-enumizer, but i was in a hurry, so i implemented the features i needed instead.


struct Service<T: From<i32>> {
    counter: T,

    pub enum ServiceMsg,
    trait ServiceProxy,
    returnval = TokioRetval,
impl Service {
    pub fn increment_by(&mut self, arg: i32) -> i32 {
        self.counter = arg;

This will generate something similar to the following code:

pub enum ServiceMsg
where TokioRetval: ::ctrlgen::Returnval,
    IncrementBy {
        arg: i32,
        ret: <TokioRetval as ::ctrlgen::Returnval>::Sender<i32>,

impl ::ctrlgen::CallMut<Service> for ServiceMsg
where TokioRetval: ::ctrlgen::Returnval,
    type Output = core::result::Result<(), <TokioRetval as ::ctrlgen::Returnval>::SendError>;
    fn call_mut(self, this: &mut Service) -> Self::Output {
        match self {
            Self::IncrementBy { arg, ret } => {
                <TokioRetval as ::ctrlgen::Returnval>::send(ret, this.increment_by(arg))

pub trait ServiceProxy: ::ctrlgen::Proxy<ServiceMsg>
    fn increment_by(&self, arg: i32) -> <TokioRetval as ::ctrlgen::Returnval>::RecvResult<i32> {
        let ret = <TokioRetval as ::ctrlgen::Returnval>::create();
        let msg = ServiceMsg::IncrementBy { arg, ret: ret.0 };
        ::ctrlgen::Proxy::send(self, msg);
        <TokioRetval as ::ctrlgen::Returnval>::recv(ret.1)

impl<T: ::ctrlgen::Proxy<ServiceMsg>> ServiceProxy for T {}


By setting the returnval = <Trait> parameter, you configure the channel over which return values are sent. <Trait> must implement ctrlgen::Returnval.

Example Implementation:

pub struct FailedToSendRetVal;
pub struct TokioRetval;
impl Returnval for TokioRetval {
    type Sender<T> = promise::Sender<T>;
    type Receiver<T> = promise::Promise<T>;
    type SendError = FailedToSendRetVal;

    type RecvResult<T> = promise::Promise<T>;

    fn create<T>() -> (Self::Sender<T>, Self::Receiver<T>) {

    fn recv<T>(rx: Self::Receiver<T>) -> Self::RecvResult<T> {

    fn send<T>(tx: Self::Sender<T>, msg: T) -> core::result::Result<(), Self::SendError> {
        tx.send(msg).map_err(|_| FailedToSendRetVal)


~54K SLoC