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

util: rework Either #637

Merged
merged 6 commits into from
Feb 17, 2022
Merged

util: rework Either #637

merged 6 commits into from
Feb 17, 2022

Conversation

davidpdrsn
Copy link
Member

@davidpdrsn davidpdrsn commented Feb 8, 2022

In practice I've found Either be hard to use since it changes the error type to BoxError. That means if you combine two infallible services you get a service that, to the type system, is fallible. That doesn't work well with axum's error handling model which requires all services to be infallible and thus always return a response. So you end up having to add boilerplate just to please the type system.

Additionally, the fact that Either implements Future also means we cannot fully remove the dependency on pin-project since pin-project-lite doesn't support tuple enum variants, only named fields.

This PR reworks Either to address these:

  • It now requires the two services to have the same error type so no type information is lost. I did consider doing something like where B::Error: From<A::Error> but I hope this simpler model will lead to better compiler errors.
  • Changes the response future to be a struct with a private enum using pin-project-lite
  • Removes the Future impl so we can remove the dependency on pin-project

Goes without saying that this is a breaking change so we have to wait until tower 0.5 to ship this.

cc @jplatte

Fixes #594
Fixes #550

@davidpdrsn davidpdrsn added the A-util Area: The tower "util" module label Feb 8, 2022
@jplatte
Copy link
Contributor

jplatte commented Feb 8, 2022

Have you considered implementing Layer and Service for futures_util::future::Either instead and using that type in the relevant places? That approach seemed a little bit cleaner to me when going over the possible solutions last time.

@jplatte
Copy link
Contributor

jplatte commented Feb 8, 2022

Additionally, IIRC there was some talk about changing A and B to Left and Right, which should maybe be part of the same PR?

@davidpdrsn
Copy link
Member Author

Have you considered implementing Layer and Service for futures_util::future::Either instead and using that type in the relevant places? That approach seemed a little bit cleaner to me when going over the possible solutions last time.

That would mean tower gets a public dependency on futures_util which I don't think we have today 🤔

Additionally, IIRC there was some talk about changing A and B to Left and Right, which should maybe be part of the same PR?

Yeah that seems fine to me. I'll do that.

@olix0r olix0r added this to the 0.5 milestone Feb 8, 2022
@hawkw
Copy link
Member

hawkw commented Feb 8, 2022

Have you considered implementing Layer and Service for futures_util::future::Either instead and using that type in the relevant places? That approach seemed a little bit cleaner to me when going over the possible solutions last time.

That would mean tower gets a public dependency on futures_util which I don't think we have today

FWIW, tower does depend on futures_util, but it's not exposed in any public APIs as far as I know.

@davidpdrsn
Copy link
Member Author

davidpdrsn commented Feb 8, 2022

Actually implementing Service and Layer directly for futures_util::future::Either would require putting those impls in tower-service and tower-layer and probably get in the way of making them 1.0, at least until futures_util itself is 1.0. Dunno what the timeline is on that but I doubt it'll happen any time soon.

@hawkw
Copy link
Member

hawkw commented Feb 8, 2022

Actually implementing Service and Layer directly for futures_util::future::Either would require putting those impls in tower-service and tower-layer and probably get in the way of making them 1.0, at least until futures_util itself is 1.0. Dunno what the timeline is on that but I doubt it'll happen any time soon.

Oh, you're right, I didn't think through the orphan rules here. I don't think we'll be able to do that, then.

We could use futures_util::future::Either instead of the custom response future type, but I don't think it's worth making it public API for that purpose. The implementation is trivial enough that we don't need to use it from futures_util, I think the only motivation would be compatibility, and compatibility would require putting it in tower-service, which I don't think we should do.

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

seems good to me (in 0.5, of course)

tower/src/util/either.rs Show resolved Hide resolved
tower/src/util/either.rs Show resolved Hide resolved
tower/src/util/either.rs Show resolved Hide resolved
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
@olix0r
Copy link
Collaborator

olix0r commented Feb 8, 2022

I'm generally +1 on this. we should do 0.5 sooner than later...

The CI failure seems legit, though.

@hawkw hawkw enabled auto-merge (squash) February 17, 2022 20:04
@hawkw hawkw merged commit 522687c into master Feb 17, 2022
@hawkw hawkw deleted the rework-either branch February 17, 2022 20:09
davidpdrsn added a commit to tokio-rs/axum that referenced this pull request Jan 14, 2023
Tower already has `option_layer` for conditionally applying a `Layer`
but since it uses `tower::util::Either` it always changes the error type
to `BoxError`. That requires using `HandleErrorLayer` which is
inconvenient for axum users.

That has been changed in tower
(tower-rs/tower#637) but still has some issues
(tower-rs/tower#665). Its also a breaking
change so hasn't been released yet.

In the meantime I figure we can provide our own thing in axum-extra,
since we already have an `Either` type there.
davidpdrsn added a commit to tokio-rs/axum that referenced this pull request Jan 14, 2023
Tower already has `option_layer` for conditionally applying a `Layer`
but since it uses `tower::util::Either` it always changes the error type
to `BoxError`. That requires using `HandleErrorLayer` which is
inconvenient for axum users.

That has been changed in tower
(tower-rs/tower#637) but still has some issues
(tower-rs/tower#665). Its also a breaking
change so hasn't been released yet.

In the meantime I figure we can provide our own thing in axum-extra,
since we already have an `Either` type there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-util Area: The tower "util" module
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Switch from pin-project to pin-project-lite Consider making Either uniform with futures::Either
4 participants