Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid boxing in Connector chains #919

Merged
merged 1 commit into from
Jan 4, 2025
Merged

Avoid boxing in Connector chains #919

merged 1 commit into from
Jan 4, 2025

Conversation

algesten
Copy link
Owner

@algesten algesten commented Jan 3, 2025

Close #890

@algesten algesten changed the title Avoid boxing in DefaultConnector chain Avoid boxing in Connector chains Jan 3, 2025
@algesten algesten force-pushed the fix/no-boxing branch 3 times, most recently from 7fafe3a to 2c2af87 Compare January 3, 2025 23:00
@algesten algesten merged commit 599b8c1 into main Jan 4, 2025
132 checks passed
@algesten algesten deleted the fix/no-boxing branch January 4, 2025 08:29
Comment on lines +9 to +47
pub struct ChainedConnector<In, First, Second>(First, Second, PhantomData<In>);

impl<In, First, Second> Connector<In> for ChainedConnector<In, First, Second>
where
In: Transport,
First: Connector<In>,
Second: Connector<First::Out>,
{
type Out = Second::Out;

fn connect(
&self,
details: &super::ConnectionDetails,
chained: Option<In>,
) -> Result<Option<Self::Out>, crate::Error> {
let f_out = self.0.connect(details, chained)?;
self.1.connect(details, f_out)
}
}

impl<In, First, Second> ChainedConnector<In, First, Second> {
pub(crate) fn new(first: First, second: Second) -> Self {
ChainedConnector(first, second, PhantomData)
}
}

impl<In, First, Second> fmt::Debug for ChainedConnector<In, First, Second>
where
In: Transport,
First: Connector<In>,
Second: Connector<First::Out>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("ChainedConnector")
.field(&self.0)
.field(&self.1)
.finish()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way would be to implement this for tuples of sizes 0 to 8 through a macro.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean as an alternative syntax to .chain().chain().chain()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I don't know how much extra it aids clarity though?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This avoids ugly ChainedConnector<First, ChainedConnnector<Second, ChainedConnector<...,...>>> types and would instead allow:

ChainedConnector<(First, Second, Third, ...)>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Changing a bit the DefaultConnector code to create a type error leads to this. The full type is ChainedConnector<(), ChainedConnector<(), ChainedConnector<(), ChainedConnector<(), ChainedConnector<(), ChainedConnector<(), (), WarnOnNoSocksConnector>, TcpConnector>, RustlsConnector>, WarnOnMissingTlsProvider>, WarnOnMissingTlsProvider>, ConnectProxyConnector>

It also means that DefaultConnector boxes the inner instead of having a more explicit type (and self-documenting)

#[derive(Debug)]
pub struct DefaultConnector {
    inner: ChainedConnector<(), (
        #[cfg(feature = "_test")]
        test::TestConnector,
        #[cfg(feature = "socks-proxy")]
        SocksConnector,
        #[cfg(feature = "socks-proxy")]
        WarnOnNoSocksConnector,
        TcpConnector,
        #[cfg(feature = "rustls")]
        RustlsConnector,
        #[cfg(feature = "native-tls")]
        NativeTlsConnector,
        #[cfg(feature = "_tls")]
        WarnOnMissingTlsProvider,
    )>,
}

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that just the nature of combinators in Rust? Like every single thing on an Iterator produces some intermediary type.

Copy link
Contributor

@sosthene-nitrokey sosthene-nitrokey Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iterators are very rarely stored and named. And given the combinators used with iterator include closures, you wouldn't be able to name them anyways. Here the we combine named types, and there are only two types of combinators: Chaining and Either. I'm not suggesting doing this for Either because it's much more annoying to generate many enum types. Chaining is pretty well suited to being made easy to name.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just using Iterator as example. Another is something like warp https://crates.io/crates/warp

Copy link
Contributor

@sosthene-nitrokey sosthene-nitrokey Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More recently, xilem, axum and leptos do what I'm thinking about, implementing traits on many tuple length through a macro.

I don't think this is something very important. It's still possible to build combinators in a downstream crate.

@sosthene-nitrokey
Copy link
Contributor

Thank you! 

I'll try find the time to see if this fits our use case this month.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rethink the Transport boxing
2 participants