`FollowRedirect`: Move `Policy::clone_body` to a separate trait

Open
#125 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Stale
Tech stack
rust

Research direction

Start by tracing FollowRedirect, Policy::clone_body, and the PolicyExt methods described in the issue. Review the proposed TryClone trait and the specialization and API alternatives before deciding on the design. Done means FollowRedirect uses the selected cloning approach and the existing Policy and PolicyExt API implications are addressed.

Written by the indexing model from the issue text.

Description

E-hard I-needs-decision

Feature Request

Motivation

FollowRedirect tries to clone the request bodies using Policy::clone_body function. But I believe that there is usually no more than one way to clone a body for each body type, so ideally clone_body should be be implemented once for a body type, rather than adding .and::<_, _, ()>(clone_body_fn(...)) to every instance of impl Policy. Additionally, removing clone_body from Policy<B, E> lets us remove the B parameter, which in turn removes the B parameter of PolicyExt methods (which might also make it possible to bring the extension trait methods back to Policy trait (https://github.com/tower-rs/tower-http/pull/79#discussion_r598262626), though I haven't experimented with it yet).

Also, currently, you have to manually implement Policy::<B, _>::clone_body even if B implements Clone. It would be great if FollowRedirect automatically clones the body when B: Clone.

Proposal

Introduce TryClone (or whatever) trait that tries to clone self (request body):

pub trait TryClone: Sized {
    fn try_clone(&self) -> Option<Self>;
}

// User code

pub enum MyBody {
    Stream(/* ... */),
    Full(/* ... */),
}

impl TryClone for MyBody {
    fn try_clone(&self) -> Option<Self> {
        match self {
            MyBody::Stream(/* ... */) => None,
            MyBody::Full(/* ... */) => Some(MyBody::Full(/* ... */))
        }
    }
}

And make FollowRedirect use this trait instead of Policy::clone_body function and remove that function.

When #![feature(stabilization) (https://github.com/rust-lang/rust/issues/31844) lands, we can implement the trait for any B where B: Clone:

pub trait TryClone: Sized {
    fn try_clone(&self) -> Option<Self>;
}

impl<T> TryClone for T {
    default fn try_clone(&self) -> Option<Self> {
        None
    }
}

impl<T: Clone> TryClone for T {
    fn try_clone(&self) -> Option<Self> {
        Some(self.clone())
    }
}
Drawbacks

In user code, TryClone cannot be implemented for types from third party crates and, without specialization, you would end up being unable to clone, for example, an http_body::Full. But tower_http can implement TryClone for http_body's body types in the meantime.

Even with specialization, the trait cannot be implemented for non-Clone third-party body types. However, I expect its impact to be small in practice because few (if any) body types out there expose interfaces that let third party code implement clone_body. For example, hyper::Body internally has Once variant which could in theory be cloned whereas hyper::Body itself is not Clone, but in fact there is no way for third party code to reference the variant and clone it.

Though, it should be noted that if hyper adds GenericBody and EitherBody types (https://github.com/hyperium/hyper/issues/2345#issuecomment-734982106) and makes their variants public, they would be affected by this drawback. But if they live under tower_http instead (EitherBody surely belongs there at least), we can simply implement TryClone for them in tower_http.

Also, this suggestion relies on specialization feature (instead of min_specialization as it stands), whose path towards stabilization is still very unclear.

Alternatives

Keep the current API as-is. We may well want to wait until specialization feature stabilizes. Also, we can apply the specialization trick in the proposal even while keeping the API in its current form, by making TryClone trait private and using TryClone::try_clone as a fallback for Policy::clone_body.

Dominant language
Rust
Stars
913
Forks
231
Avg merge
1d 20h
Merged PRs (30d)
8

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from tower-rs/tower-http

All issues in tower-rs/tower-http

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.