Extention traits on traits

Hi! Are there any good resources on using extension traits on other traits? I've found this blog post, Extending the Iterator Trait in Rust | Bryan Burgers, but not much else.

EX:

trait ToMyIterator: Iterator + Sized {
   fn myfunction(self) -> MyIterator<Self> {
        MyIterator::new(self)
    }
}
impl<I: Iterator> for I {}

So I'm extending the iterator trait with my trait here. I just want to know more about how that is working and how the associated type is being handled and how this is different than something like

// I know this doesn't work, just an example
impl ToMyIterator for Iterator {}

or

// Also doesn't work, needs specification on Item
impl ToMyIterator for dyn Iterator {}
impl<T: Iterator + Sized> ToMyIterator for T {
   // ...
}

The actual implementation needs to go in the impl block rather than as a default impl.

What I have in the first block works:

trait ToMyIterator: Iterator + Sized {
   fn myfunction(self) -> MyIterator<Self> {
        MyIterator::new(self)
    }
}
impl<I: Iterator> for I {}

I just want more resources regarding trait extensions, or like a book on "advanced OOPish stuff in Rust"

Here, let's walk through this code:

trait ToMyIterator: Iterator + Sized {
   fn myfunction(self) -> MyIterator<Self> {
        MyIterator::new(self)
    }
}
impl<I: Iterator> for I {}
  1. trait ToMyIterator: Iterator + Sized 
    
    You're saying that all types which implement ToMyIterator must also implement Iterator, and be sized.
  2. fn myfunction(self) -> MyIterator<Self> {
        MyIterator::new(self)
    }
    
    A trait function with a default method implementation (please run your code through rustfmt in the future please!).
  3. impl<I: Iterator> for I {}
    
    This line, as it is, makes no sense, but I assume you meant something like this:
    impl<I: Iterator> ToMyIterator for I {}
    
    Note that this means that all types which implement Iterator, will also now implement your ToMyIterator trait. This is, to some effect, extending the Iterator trait, since you're doing such a broad blanket implementation.

Now, about your other code examples:

impl ToMyIterator for Iterator {}

This is technically equivalent to

impl ToMyIterator for dyn Iterator {}

Since in type position (which is to the right of for in impl Trait for Type), trait names are automatically assumed to be trait objects (dyn Trait), however it's bad practice to omit the dyn and will eventually become a hard error.

If you were asking about literally "extending" the stdlib iterator trait, you can't. Traits defined in other crates cannot be changed, and instead you can only do a blanket implementation, so that you are emulating the extension.

Finally, your last example:

impl ToMyIterator for dyn Iterator {}

Would be implementing your trait for the trait object. dyn Iterator represents "some value of unknown type which implements the Iterator trait". Implementing it like so would result in the implementation only applying to values which have been cast into a trait object. Note that I can do this:

trait Foo {
    fn print(&self);
}

impl Foo for usize {
    fn print(&self) {
        println!("We're printing for usize");
    }
}

impl Foo for dyn Debug {
    fn print(&self) {
        println!("We're printing for dyn Debug");
    }
}

fn main() {
    let x = 0usize;
    x.print(); // We're printing for usize
    (&x as &dyn Debug).print(); // We're printing for dyn Debug
}

Playground.

4 Likes

Thanks for powering through my poor examples!

I understand what you are saying. The key takeaway for me was that a dyn Trait trait object is itself a type that is distinct from a generic type that has Type as a bound.

2 Likes

If anyone has any further resources on trait extension patterns I would love to see them!

The reference calls these "supertraits". Its probably easier if you see this syntax as sugar for the syntax that uses the where clause.

trait Circle : Shape { fn radius(&self) -> f64; }

Is this same as...

trait Circle where Self: Shape { fn radius(&self) -> f64; }

The reference also has examples of using the supertrait methods on generic traits and trait objects.

https://doc.rust-lang.org/stable/reference/items/traits.html?highlight=extension%20trait#supertraits

Interesting. It's good to have a word to put to that, but I think it's not quite the same thing as what I'm trying to do, it's just coincidence that my sample is using a supertrait.

In your example, if you were turn around an implement Circle for all Shape, that is what I want to learn more about.

"Blanket implementation" is the key phrase for the pattern; in particular you're talking about the case where the implementation has a bound on another trait. E.g. from @OptimisticPeach's walkthrough,

impl<I: Iterator> ToMyIterator for I {}

Or:

// Same thing different phrasing
impl<I> ToMyIterator for I where I: Iterator {}

You can find some examples in the documentation, e.g. this page has an entry under Implementors for

impl<T> ToString for T where T: Display + ?Sized

and you can click on the src link to see the code.

(Example cribbed from @alice via this previous thread.)

2 Likes