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

codec: change Encoder to take &Item #1746

Merged
merged 3 commits into from
Mar 4, 2020
Merged

Conversation

djc
Copy link
Contributor

@djc djc commented Nov 7, 2019

Since the encoded item is to be encoded into a given BytesMut, taking ownership is not necessary here.

@djc
Copy link
Contributor Author

djc commented Nov 7, 2019

As discussed here: tokio-rs/tokio-io#54.

@Marwes
Copy link
Contributor

Marwes commented Nov 7, 2019

Since this is an input argument, then Item should probably be a type parameter instead of an associated type? If that is the case then the implementer can freely specify reference vs non-reference.

@hawkw
Copy link
Member

hawkw commented Nov 7, 2019

I agree with @Marwes' comment — I think the current design of this trait may be an artifact of older versions of the Sink trait, which had an associated type for their inputs rather than a type parameter...

@djc
Copy link
Contributor Author

djc commented Nov 8, 2019

So I've changed the Item to be a type parameter instead of an associated type (see the second commit in the series). However, I'm not sure if the second step (allow ownership transfer or just passing a reference) is actually possible because I don't how to express that any lifetime bound is allowed. Here's an example error:

error[E0716]: temporary value dropped while borrowed
   --> tokio-util/tests/length_delimited.rs:399:14
    |
399 |     let io = length_delimited::Builder::new()
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
...
405 |         });
    |           - temporary value is freed at the end of this statement
406 |     pin_mut!(io);
    |     ------------- borrow later used here
    |
    = note: consider using a `let` binding to create a longer lived value
    = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

This seems to happen because the FramedWrite (for example) gets returned with a lifetime matching the Builder lifetime. I tried to:

  • use higher-ranked trait bounds to get the compiler to unlink the lifetimes
  • explicitly used lifetime names to unlink the lifetimes

but neither of these approaches had the desired effect. Any suggestions?

@djc
Copy link
Contributor Author

djc commented Nov 9, 2019

I did yet another attempt with using AsRef<Item>, but it was pretty ugly. I'm really not sure of the value of allowing both ownership and references to be passed?

@@ -18,5 +18,5 @@ pub trait Encoder {
/// This method will encode `item` into the byte buffer provided by `dst`.
/// The `dst` provided is an internal buffer of the `Framed` instance and
/// will be written out when possible.
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error>;
fn encode(&mut self, item: &Item, dst: &mut BytesMut) -> Result<(), Self::Error>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Didn't think about the lifetime issues :/ I am not able to decide on this obviously but &Item with Item as an argument seems like the way to go 👍

@djc
Copy link
Contributor Author

djc commented Nov 9, 2019

Do you have some kind of use case where you'd prefer to pass ownership?

@Marwes
Copy link
Contributor

Marwes commented Nov 9, 2019

No, just thought it would work for both cases.

@djc
Copy link
Contributor Author

djc commented Nov 18, 2019

Ping! Any further feedback before I spend time on rebasing this?

@carllerche
Copy link
Member

This seems good to me. I can queue it up to be merged w/ tokio-util 0.3 (which can be sooner than later).

@djc djc force-pushed the encoder-ref branch 2 times, most recently from 78add5f to 9cf3149 Compare November 27, 2019 20:07
@djc
Copy link
Contributor Author

djc commented Nov 27, 2019

(Rebase not quite finished, will continue later, pending taiki-e/pin-project#170.)

@djc
Copy link
Contributor Author

djc commented Nov 29, 2019

It looks like pin-project-lite does not support ?Sized bounds, and I think it's not very obvious how to support these in declarative macros. So I'm not sure how to continue rebasing this.

@djc
Copy link
Contributor Author

djc commented Dec 6, 2019

@carllerche ping? The question here is if it would be okay to revert to pin-project instead of pin-project-lite for tokio-util in order to make this work.

@carllerche
Copy link
Member

I would rather avoid either then (use unsafe). pin-project is too heavy at this point IMO.

@taiki-e
Copy link
Member

taiki-e commented Jan 20, 2020

@djc
Copy link
Contributor Author

djc commented Jan 20, 2020

@taiki-e that's great, thanks!

So I rebased this on top of current master, keeping the use of pin-project-lite intact.

However, iterating on this I question the change going from Encoder<Item = I> to Encoder<I>: it appears to me that in the latter case, the type used for I must be specified explicitly for types like Framed, requiring a third type parameter everywhere (otherwise I get the not constrained by the impl trait, self type, or predicates error for some of the Framed impls).

IMO, the current situation, where the Encoder implementation specifies a single associated type and thus Framed instantiations do not have to repeat the Encoder's item type, is more ergonomic to use. I've pushed a second commit that changes this back, and would like some feedback on the preferable direction before I go through all the example code to fix it up if necessary.

@LucioFranco
Copy link
Member

I wonder if it makes sense to make Item a generic on the trait which then would allow you to use for<'a>: Encoder<&'a T>. This could enable both scenarios.

@djc
Copy link
Contributor Author

djc commented Jan 21, 2020

@LucioFranco do you think that would fix the issues from #1746 (comment)? This feels like we're going back and forth. Also, if Item is generic on the trait, don't Framed and friends still have to specify the Encoder's Item type explicitly?

@Marwes
Copy link
Contributor

Marwes commented Jan 21, 2020

Passing Item just as a type parameter without any & works fine at least https://github.com/Marwes/tokio/tree/type_parameter_encoder .

@djc
Copy link
Contributor Author

djc commented Jan 21, 2020

But in that approach can I also pass in a &'a MyStruct? Would AsRef<MyStruct> also work for that?

@djc
Copy link
Contributor Author

djc commented Jan 21, 2020

Also the requirement to parametrize the Sink method calls with the item type seems a little ugly.

@Marwes
Copy link
Contributor

Marwes commented Jan 21, 2020

But in that approach can I also pass in a &'a MyStruct? Would AsRef also work for that?

Sure. See LinesCodec and BytesCodec which take I: AsRef<_>. &'a MyStruct works with the suggestion in #1746 (comment) E: for<'a> Encoder<&'a MyStruct>

Also the requirement to parametrize the Sink method calls with the item type seems a little ugly.

That is only because start_send/poll_flush is called which do not refer to Item. For normal use Item is passed and everything is figured out.

@Marwes
Copy link
Contributor

Marwes commented Jan 21, 2020

The main downside is that some Encoder<I> bounds has to be removed to let type inference work without needing to specify Framed::new::<&str> etc.

@djc
Copy link
Contributor Author

djc commented Jan 21, 2020

The main downside is that some Encoder<I> bounds has to be removed to let type inference work without needing to specify Framed::new::<&str> etc.

How is that a downside, exactly?

@Marwes
Copy link
Contributor

Marwes commented Jan 21, 2020

If you accidentally pass something that does not implement Encoder then the error message ends up further away from where the actual problem is. Not a huge issue but I had this problem in https://github.com/Marwes/combine and had to stick PhantomData everywhere due to inference problems like this.

It is probably not that big of an issue here but in combine Parser<Input> types are so deeply nested that omitting the bound was enough of an issue that I had to keep it (and keep PhantomData around everywhere to fix the type parameter)

@djc djc force-pushed the encoder-ref branch 2 times, most recently from c45296c to 8bd8a83 Compare February 21, 2020 14:22
@djc
Copy link
Contributor Author

djc commented Feb 21, 2020

I think this is ready for an actual review now.

@Marwes thanks for your changes. I've squashed our changes together, but added you as a co-author to the resulting commit.

Copy link
Member

@LucioFranco LucioFranco left a comment

Choose a reason for hiding this comment

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

Looks good overall just two questions

tokio-util/src/codec/decoder.rs Show resolved Hide resolved
@@ -546,12 +543,12 @@ impl Decoder for LengthDelimitedCodec {
}
}

impl Encoder for LengthDelimitedCodec {
type Item = Bytes;
impl<T> Encoder<T> for LengthDelimitedCodec where T: AsRef<[u8]> {
Copy link
Member

Choose a reason for hiding this comment

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

I think it may make sense to keep this Encoder<Bytes>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why do you think so? This is strictly more general.

@djc djc force-pushed the encoder-ref branch 2 times, most recently from 3aa5a94 to 0a705a8 Compare February 28, 2020 19:53
Copy link
Member

@LucioFranco LucioFranco left a comment

Choose a reason for hiding this comment

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

This looks mostly good just a few questions, once we get this merged I'll get a 0.3 release out. Sorry for the delay!

Copy link
Member

@LucioFranco LucioFranco left a comment

Choose a reason for hiding this comment

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

👍 I want to see LengthDelimitedCodec take a Bytes as well. Then we can get this release out, sorry for the super long delay on all this!

djc and others added 2 commits March 4, 2020 21:10
This allows passing references into `Encoder`s, which are
going to encode the item into a given `BytesMut` anyway.

Co-authored-by: Markus Westerlind <markus.westerlind@distilnetworks.com>
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.

6 participants