-
Notifications
You must be signed in to change notification settings - Fork 184
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
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,133 @@ | ||
use crate::Error; | ||
use std::fmt; | ||
use std::marker::PhantomData; | ||
|
||
use super::{ConnectionDetails, Connector, Transport}; | ||
use super::{Connector, Transport}; | ||
|
||
/// Helper for a chain of connectors. | ||
/// Two chained connectors called one after another. | ||
/// | ||
/// Each step of the chain, can decide whether to: | ||
/// | ||
/// * _Keep_ previous [`Transport`] | ||
/// * _Wrap_ previous [`Transport`] | ||
/// * _Ignore_ previous [`Transport`] in favor of some other connection. | ||
/// | ||
/// For each new connection, the chain will be called one by one and the previously chained | ||
/// transport will be provided to the next as an argument in [`Connector::connect()`]. | ||
/// | ||
/// The chain is always looped fully. There is no early return. | ||
/// Created by calling [`Connector::chain`] on the first connector. | ||
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() | ||
} | ||
} | ||
|
||
/// A selection between two transports. | ||
#[derive(Debug)] | ||
pub struct ChainedConnector { | ||
chain: Vec<Box<dyn Connector>>, | ||
pub enum Either<A, B> { | ||
/// The first transport. | ||
A(A), | ||
/// The second transport. | ||
B(B), | ||
} | ||
|
||
impl ChainedConnector { | ||
/// Creates a new chain of connectors. | ||
/// | ||
/// For each connection, the chain will be called one by one and the previously chained | ||
/// transport will be provided to the next as an argument in [`Connector::connect()`]. | ||
/// | ||
/// The chain is always looped fully. There is no early return. | ||
pub fn new(chain: impl IntoIterator<Item = Box<dyn Connector>>) -> Self { | ||
Self { | ||
chain: chain.into_iter().collect(), | ||
impl<A: Transport, B: Transport> Transport for Either<A, B> { | ||
fn buffers(&mut self) -> &mut dyn super::Buffers { | ||
match self { | ||
Either::A(a) => a.buffers(), | ||
Either::B(b) => b.buffers(), | ||
} | ||
} | ||
|
||
fn transmit_output( | ||
&mut self, | ||
amount: usize, | ||
timeout: super::NextTimeout, | ||
) -> Result<(), crate::Error> { | ||
match self { | ||
Either::A(a) => a.transmit_output(amount, timeout), | ||
Either::B(b) => b.transmit_output(amount, timeout), | ||
} | ||
} | ||
|
||
fn await_input(&mut self, timeout: super::NextTimeout) -> Result<bool, crate::Error> { | ||
match self { | ||
Either::A(a) => a.await_input(timeout), | ||
Either::B(b) => b.await_input(timeout), | ||
} | ||
} | ||
|
||
fn is_open(&mut self) -> bool { | ||
match self { | ||
Either::A(a) => a.is_open(), | ||
Either::B(b) => b.is_open(), | ||
} | ||
} | ||
|
||
fn is_tls(&self) -> bool { | ||
match self { | ||
Either::A(a) => a.is_tls(), | ||
Either::B(b) => b.is_tls(), | ||
} | ||
} | ||
} | ||
|
||
impl Connector for ChainedConnector { | ||
// Connector is implemented for () to start a chain of connectors. | ||
// | ||
// The `Out` transport is supposedly `()`, but this is never instantiated. | ||
impl Connector<()> for () { | ||
type Out = (); | ||
|
||
fn connect( | ||
&self, | ||
details: &ConnectionDetails, | ||
chained: Option<Box<dyn Transport>>, | ||
) -> Result<Option<Box<dyn Transport>>, Error> { | ||
let mut conn = chained; | ||
_: &super::ConnectionDetails, | ||
_: Option<()>, | ||
) -> Result<Option<Self::Out>, crate::Error> { | ||
Ok(None) | ||
} | ||
} | ||
|
||
for connector in &self.chain { | ||
conn = connector.connect(details, conn)?; | ||
} | ||
// () is a valid Transport for type reasons. | ||
// | ||
// It should never be instantiated as an actual transport. | ||
impl Transport for () { | ||
fn buffers(&mut self) -> &mut dyn super::Buffers { | ||
panic!("Unit transport is not valid") | ||
} | ||
|
||
fn transmit_output(&mut self, _: usize, _: super::NextTimeout) -> Result<(), crate::Error> { | ||
panic!("Unit transport is not valid") | ||
} | ||
|
||
fn await_input(&mut self, _: super::NextTimeout) -> Result<bool, crate::Error> { | ||
panic!("Unit transport is not valid") | ||
} | ||
|
||
Ok(conn) | ||
fn is_open(&mut self) -> bool { | ||
panic!("Unit transport is not valid") | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, ...)>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing a bit the
DefaultConnector
code to create a type error leads to this. The full type isChainedConnector<(), ChainedConnector<(), ChainedConnector<(), ChainedConnector<(), ChainedConnector<(), ChainedConnector<(), (), WarnOnNoSocksConnector>, TcpConnector>, RustlsConnector>, WarnOnMissingTlsProvider>, WarnOnMissingTlsProvider>, ConnectProxyConnector>
It also means that
DefaultConnector
boxes theinner
instead of having a more explicit type (and self-documenting)There was a problem hiding this comment.
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.There was a problem hiding this comment.
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
andEither
. I'm not suggesting doing this forEither
because it's much more annoying to generate many enum types.Chaining
is pretty well suited to being made easy to name.There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.