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

feat(rt/tokio): additive tokio and hyper i/o adaptors #170

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

cratelyn
Copy link
Contributor

this commit introduces two new i/o adaptors, akin to rt::tokio::TokioIo<I>.

see this small program demonstrating the motivation for this:

 #![allow(unreachable_code, unused_variables, dead_code)]

 use {
     hyper::rt::{Read, Write},
     hyper_util::rt::TokioIo,
     tokio::io::{AsyncRead, AsyncWrite},
     tokio::net::TcpStream,
 };

 fn is_tokio_io<I>(_: &I)
 where
     I: AsyncRead + AsyncWrite,
 {
 }

 fn is_hyper_io<I>(_: &I)
 where
     I: Read + Write,
 {
 }

 fn check_io_impls() {
     // `I: AsyncRead + AsyncWrite` does not implement hyper's `Read` and `Write`.
     let stream: TcpStream = todo!();
     is_tokio_io(&stream);
     // is_hyper_io(&stream); does not compile

     // `TokioIo<I: AsyncRead + AsyncWrite>` does not implement `tokio::io` traits.
     let stream: TokioIo<TcpStream> = TokioIo::new(stream);
     // is_tokio_io(&stream); does not compile
     is_hyper_io(&stream);

     // `TokioIo<TokioIo<I: AsyncRead + AsyncWrite>>` does not implement hyper's `Read` and `Write`.
     let stream: TokioIo<TokioIo<TcpStream>> = TokioIo::new(stream);
     is_tokio_io(&stream);
     // is_hyper_io(&stream); does not compile
 }

 fn main() {}

TokioIo<I> is not an adaptor that is additive with respect to its inner i/o transport. if it implements hyper's i/o, the wrapped type will implement tokio's at the expense of hyper's, and vice versa.

this is often fine for simple programs, but this "switching" approach can interfere with larger applications' ability to make use of middleware that is generic across different kinds of i/o streams.

this commit introduces types that allow a caller to add Read or Write implementations to tokio readers and writers, or inversely, AsyncRead and AsyncWrite implementations to hyper readers and writers.

Copy link
Member

@seanmonstar seanmonstar left a comment

Choose a reason for hiding this comment

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

I appreciate the need for the types. My only concern is about confusing users which one they need... Perhaps at the top of the Tokio module here, we can add a section about IO, and bullet points about why you would want each one. What do you think?

@cratelyn
Copy link
Contributor Author

I appreciate the need for the types. My only concern is about confusing users which one they need... Perhaps at the top of the Tokio module here, we can add a section about IO, and bullet points about why you would want each one. What do you think?

that sounds sensible to me! i'll add some documentation comparing and contrasting these types later today.

@cratelyn cratelyn force-pushed the add-io branch 2 times, most recently from 011c954 to 6a8bca9 Compare March 17, 2025 15:58
@cratelyn
Copy link
Contributor Author

pardon the delay, i've written some documentation for hyper::rt::tokio. included in these docs are a table outlining what IO traits are implemented by which types, given transport from either hyper or tokio. some advice is included outlining that TokioIo<I> is generally the correct tool for most use-cases, along with links to tokio::io's upstream documentation.

nb: this is currently based upon #174, to address a CI error at the time of writing.

this commit introduces two new i/o adaptors, akin to
`rt::tokio::TokioIo<I>`.

see this small program demonstrating the motivation for this:

```rust
 #![allow(unreachable_code, unused_variables, dead_code)]

 use {
     hyper::rt::{Read, Write},
     hyper_util::rt::TokioIo,
     tokio::io::{AsyncRead, AsyncWrite},
     tokio::net::TcpStream,
 };

 fn is_tokio_io<I>(_: &I)
 where
     I: AsyncRead + AsyncWrite,
 {
 }

 fn is_hyper_io<I>(_: &I)
 where
     I: Read + Write,
 {
 }

 fn check_io_impls() {
     // `I: AsyncRead + AsyncWrite` does not implement hyper's `Read` and `Write`.
     let stream: TcpStream = todo!();
     is_tokio_io(&stream);
     // is_hyper_io(&stream); does not compile

     // `TokioIo<I: AsyncRead + AsyncWrite>` does not implement `tokio::io` traits.
     let stream: TokioIo<TcpStream> = TokioIo::new(stream);
     // is_tokio_io(&stream); does not compile
     is_hyper_io(&stream);

     // `TokioIo<TokioIo<I: AsyncRead + AsyncWrite>>` does not implement hyper's `Read` and `Write`.
     let stream: TokioIo<TokioIo<TcpStream>> = TokioIo::new(stream);
     is_tokio_io(&stream);
     // is_hyper_io(&stream); does not compile
 }

 fn main() {}
```

`TokioIo<I>` is not an adaptor that is additive with respect to its
inner i/o transport. if it implements hyper's i/o, the wrapped type will
implement tokio's _at the expense of hyper's_, and vice versa.

this is often fine for simple programs, but this "switching" approach
can interfere with larger applications' ability to make use of
middleware that is generic across different kinds of i/o streams.

this commit introduces types that allow a caller to _add_ `Read` or
`Write` implementations to tokio readers and writers, or inversely,
`AsyncRead` and `AsyncWrite` implementations to hyper readers and
writers.

Signed-off-by: katelyn martin <me+cratelyn@katelyn.world>
Signed-off-by: katelyn martin <me+cratelyn@katelyn.world>
@cratelyn
Copy link
Contributor Author

this is no longer based upon #174. at time of writing, CI errors related to the MSRV are to be expected.

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.

2 participants