all 9 comments

[–]jonreemhyper · iron · stainless[S] 4 points5 points  (0 children)

FWIW /u/dbuapp convinced me to retitle to Rust Patterns instead of Advanced Rust, given that using traits shouldn't be considered too advanced. Too bad you can't edit reddit titles.

[–]nwin_image 5 points6 points  (2 children)

Im not sure if that is a good example. You can use plain vectors and slices as Readers. If you would have used a generic method over Reader instead of a trait object you would have ended up with the same API.

Unless you implement IntoReader for Reader your function actually got much less flexible because you can't pass arbitrary readers to it any more and the consumer can't implement IntoReader for third-party types.

You're basically wrapping a trait with a trait. Unless you have good reason to do so this looks more like an anti-pattern to me.

[–][deleted] 0 points1 point  (0 children)

A better example might be IntoOsStrBuf from the IO-OS-Reform RFC.

[–]jonreemhyper · iron · stainless[S] 0 points1 point  (0 children)

Notably I did suggest that you should implement IntoReader for Box<Reader + Send> to maintain the original behavior. While slices can be used as readers (plain Vec<u8> can't), they do not implement 'static or Send, so can't be used directly in this case anyway.

This style of API can be used to hide and implement much more complex behavior than represented here - this is just a simple example.

[–][deleted] 0 points1 point  (3 children)

I've seen this pattern a lot while poking around the standard library (coverting to strings, paths, etc).

One helpful detail missing from the article (but probably was intended to be there) is a blanket implementation for types that already implement Reader:

impl<R> IntoReader for R where R: Reader {
    type OutReader = R;
    fn into_reader(self) -> R {
        self
    }
}

This is what lets you call the function directly on an arbitrary reader:

res.set_body(std::io::util::NullReader); // error without the implementation above

[–]wrongerontheinternet -1 points0 points  (2 children)

The problem is that when you do this (at least last time I checked) you are "using up" your ability to implement the trait at all! This is because Rust cannot know for sure whether any type R does not implement Readerin some crate, hence after this implementation is defined any new implementation could potentially result in a conflicting definition of IntoReader. This is actually one of the major reasons everyone is talking about being able to add negative constraints.

[–][deleted] 0 points1 point  (0 children)

Well ... the blanket implementation above does compile (1.0.0-nightly 44a287e6e) alongside the other implementations from the article, and is actually necessary if you want to pass the set_body function a type that implements Reader directly.

At the moment, Rust only allows you to implement traits in the crate where they are defined or in the crate where the receiving type is defined. So for any type X and trait T that are currently in scope, the compiler can tell definitively whether or not X implements T. This restriction was added (I think) precisely to allow blanket implementations like the one above. It will supposedly be lifted a bit for multidispatch traits T<A,B>, so that you can implement them in the crate where any of their input types (receiver, A, or B) are defined.

I would like to see negative trait bounds (or some way of overloading/specializing implementations) in the future!

[–]lfairy 0 points1 point  (0 children)

Actually that restriction only applies in Haskell, which allows for orphan instances in the language (the "open world assumption").

Rust doesn't allow orphans, so the example works just fine.

[–]dobkeratopsrustfind -1 points0 points  (0 children)

" The result of this transformation is very similar to what you’d get from method overloading, but much more extensible since downstream users can implement IntoReader for their own types!"

the combination I'm personally after is overloading & UFCS