A question about method chaining with different return types

I have to use an example to explain this:

the .with_congestion_controller here returns a different type than the input type and for different controllers, so I can't (or don't know how to) capture the builder in a variable, fortunately here I can just call .start() and that returns the same Server type so I can do that, but what if I need more configurations?

I guess there's a better solution/paradigm but I tried my best to search and couldn't find anything.

1 Like

I hook to the question - I have a similar problem with tracing_subscriber::FmtSubscriber.

I wanted to runtime select between normal, pretty and compact subscribers, but each branch returns different type.

I wanted

let subscriber = FmtSubscriber::builder()
        .with_max_level(match cli.verbose {
            0 => Level::INFO,
            1 => Level::DEBUG,
            _ => Level::TRACE,
        })
        .without_time();

let dflt = match (cli.compact, cli.pretty) {
            (false, false) => subscribe.finish(),
            (false, true)  => subscriber.pretty().finish(),
            (true, false)  => subscriber.compact().finish(),
            (true, true)   => subscriber.pretty().compact().finish(),
           };
tracing::subscriber::set_global_default(dflt);

but had to pull set_global_default() to each branch.

1 Like

The signature of the builder method is the following:

builder() -> Builder<impl ServerProviders>

Which means that it returns a Builder struct which is generic over anything that implements the ServerProviders trait.

If you want to hold a variable for different builders, you need to hint the compiler about that generic property.

let builder: Builder<_>  = s2n_quic::Server::builder()
 // ...

Edit:

Nvm., just gave it a try and it still complains that the types don't match.

Edit #2:

It seems that it would be possible with https://rust-lang.github.io/rfcs/2071-impl-trait-existential-types.html:

type BuilderInner = impl ServerProviders;

let builder: Builder<BuilderInner>  = s2n_quic::Server::builder()
 // ...

I'm not quite sure about this but if my understanding was ok, the current impl Trait is like a shorthand to avoid writing (maybe absurdly long) concrete types, it's not a dynamic type like Box<dyn ServerProviders> which might work here but IMHO looks cumbersome.

for example this still won't work:

1 Like

You're right. I gave it a try, and the compiler complains about the opaque types being different.

1 Like

Thanks for another example, but I believe what we really want is something like:

if cli.compact {
    s.compact();
}
if cli.pretty {
    s.pretty();
}
set_global_default(s.finish());

or better:

set_global_default(builder(). ... .pretty(cli.pretty).compact(cli.compact).finish());

unfortunately your "enumerate all combinations" approach would become absurd with more than 2 knobs.

Fortunately start()'s return type doesn't depend on the selected options; that opens up the following possibility:

trait MyBuilder {
    fn with_congestion_controller_bbr(self: Box<Self>) -> Box<dyn MyBuilder>;
    fn start(self: Box<Self>) -> s2n_quic::Server;
}

impl<P: ServerProviders + 'static> MyBuilder for s2n_quic::server::Builder<P> {
    fn with_congestion_controller_bbr(self: Box<Self>) -> Box<dyn MyBuilder> {
        Box::new(
            self.with_congestion_controller(
                s2n_quic::provider::congestion_controller::Bbr::default(),
            )
            .unwrap(),
        )
    }

    fn start(self: Box<Self>) -> s2n_quic::Server {
        (*self).start().unwrap()
    }
}

pub async fn s2n_server(bind: SocketAddr, bbr: bool, max_snd_buf: u32) {
    let mut builder: Box<dyn MyBuilder> = Box::new(
        s2n_quic::Server::builder()
            .with_tls((CERT_PEM, KEY_PEM))
            .unwrap()
            .with_io(bind)
            .unwrap()
            .with_limits(s2n_limits(max_snd_buf))
            .unwrap(),
    );
    if bbr {
        builder = builder.with_congestion_controller_bbr();
    }
    let mut server = builder.start();
    eprintln!("listening on {}", server.local_addr().unwrap());
    // ...
}

Edit: I'm now surprised it didn't blow up the build by infinitely expanding Builder's <P>. This may not scale well to multiple options.

1 Like

Thanks for the reply, yeah that is working, but, do you agree this is comically convoluted though :sweat_smile:

Absolutely. It'd be better for the library to provide a more friendly builder.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.