hyper: Don't use private traits in public APIs
For example, hyper master currently has this:
impl<I, B, S, E> Future for Connection<I, S, E>
where
S: HttpService<Body, ResBody=B>,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
I: AsyncRead + AsyncWrite + Unpin + 'static,
B: Payload + 'static,
B::Data: Unpin,
E: H2Exec<S::Future, B>,
HttpService
and H2Exec
are both private traits, though. This makes it a huge pain for code working generically with connections to express the proper bounds in their code. You basically have to try to reverse engineer the impl bounds by bouncing off the compiler đŚ
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 11
- Comments: 49 (31 by maintainers)
Commits related to this issue
- feat(rt): make private executor traits public (but sealed) in `rt::bounds` (#3127) Define public trait aliases that are sealed, but can be named externally, and have documentation showing how to prov... — committed to hyperium/hyper by programatik29 a year ago
- feat(rt): make private executor traits public (but sealed) in `rt::bounds` (#3127) Define public trait aliases that are sealed, but can be named externally, and have documentation showing how to prov... — committed to 0xE282B0/hyper by programatik29 a year ago
- feat(rt): make private executor traits public (but sealed) in `rt::bounds` (#3127) Define public trait aliases that are sealed, but can be named externally, and have documentation showing how to prov... — committed to 0xE282B0/hyper by programatik29 a year ago
As Iâve done similar spelunking to remain generic over
Client
and thereforeConnect
andPayload
, I would also greatly appreciate fully public signatures, even if more immediately complex as in @sfacklerâs last.That you are considering the now visible
Unpin
bounds seems to support the value just for hyper API maintenance.We are working on plans for hyper 1.0, which should greatly improve on all of this.
FYI, v0.13.1 is just released that exposes
hyper::client::connect::Connect
.Though this brings up an interesting thought, since
hyper::Client
allows making requests with a shared reference, maybe hyper shouldimpl Service for &'_ Client
as wellâŚI ended up with this for a struct that allows hyper
Client
s with different connectors:IMO private types in a public signature are even more âscaryâ. At best they seem like an oversight or unfinished work.
Could there be some separate public type aliases, with tests confirming compatibility with the internal, non-public types (which may span multiple crates)? Just rustdoc text is unlikely to improve confidence, particularly if it becomes out of date.
Honestly after trying to mess around with the API for a bit, I can safely say that the lack of public types makes things a nightmare to deal with. Like, with all due respect, trying to fenangle the types for
MakeServiceFn
and co. was the most miserable experience Iâve ever had trying to work with a Rust library.IMHO, if the types are too complicated to work with internally, then theyâre too complicated to work with externally too. Aliases that are helpful internally should be packaged and offered externally.
I do think that using
impl trait
more often could seriously help with the types for things, although I personally tend to be of the mindset that using newtypes is generally better for public APIs as it lets you name the types more easily.Mostly just my 2¢. Havenât read the full thread and havenât done a whole lot of work in Rust recently, so, may not be the most representative opinion of other users of the library.
I just wanted to say +1 to making the Connect trait alias publicly visible again. Making the trait visible (even while still not allowing others to implement it) seems better in all ways than the current form. First it makes it possible to write generic bounds against it, second it can show up in docs which will also highlight the blanket impl for tower::Service along with an opportunity to write a comment that tower::Service is what should be implemented. Right now the only way to know you need to implement tower::Service is to read the source code.
An issue when moving from a hyper::Client<C> to a generic tower::Service<http::Request, Response=http::Response> is that the ownership semantics between hyper and tower are not the same. Hyper has internal synchronization so that you can invoke methods on shared references. A tower service requires a mutable reference to invoke call. This means that I now need to implement my own synchronization around the tower service, even though I really just want to support hyper::Clients that already provide their own. My code would end up being much more complicated and would be doing double synchronization for the trouble.
We should! Filed https://github.com/hyperium/hyper/issues/2070
Perhaps the
Connect
âtrait aliasâ should be made public again, so people can more easily set the bounds in their own libraries?I strongly agree with this.
I was attempting to update https://github.com/ctz/hyper-rustls to the latest Hyper release on the master branch and the sealed types in trait bounds are not fun to work with or reconstruct. A big portion of this were the unimplementable trait aliases in public signatures. If Hyper is to continue exposing these trait aliases, Iâd greatly prefer that they be unsealed despite them being a potential semver hazard.
Perhaps I donât fully understand, but if there is a blanket implementation of
Connect
for everytower_service::Service
with the same generic bounds, there shouldnât be any incompatibilities resulting from some users choosing to implementConnect
and others implementingtower_service::Service
, correct?This is what I arrived at after ~1 hour of source diving into hyper and bouncing off of compiler errors as the trait bounds for Connectionâs Future implementation:
Itâs fine to use the private traits bounds internally in hyper - the parts that matter to me are just the public interfaces, where it is important to be able to see what is in fact required to call the method.
Iâm trying to write a function that returns a
Server<AddrIncoming, MakeServiceFn<F>>
butMakeServiceFn
is private, so it cannot be named. CouldMakeServiceFn
be made public?Edit: Maybe the solution is to instead return
impl Future<Output = ...>
?@dignifiedquire there is a trait alias bound that you can implement that version of
tower::Service
https://docs.rs/hyper/0.13.2/hyper/client/connect/trait.Connect.htmlbasically where
impl Service<Uri, Response = impl AsyncRead + AsyncWrite>
Thanks for the pointer @jbg, as it seems my upgrade to the last alpha (signature above) is drastically incomplete for the hyper 0.13 release. I additonally need to be generic over B: HttpBody, so thus far I have this:
Lots of
Send
,Sync
, andUnpin
in there. Also I thought hyper was trying to be agnostic to executor? But notice thatAsyncRead
andAsyncWrite
traits (which I otherwise donât care much about) are tokioâs.Youâd need to further unwrap them until they donât show private types. Anyone writing code that deals with hyper types in a generic context has to figure this stuff out anyway!
So, hyper uses these âprivate trait aliasesâ to try to make the
where
bounds more succinct. For instance, theHttpService
trait is implemented forService
s with the right types.HttpService:
Service:
I assumed that seeing less ânoiseâ would be helpful, and it also means less duplication of bounds inside the hyper codebase. If it really seems to make things worse, we can remove these âaliasesâ and just make the bounds lists bigger.