Search code examples
rustreferenceiteratorborrowing

Why am I allowed to call take() on a mutable reference to an iterator?


I was trying to use take(n) on an iterator to do something with the first n items, but then do something different (a for ... in ... loop) with the remaining items.

In the declaration:

fn take(self, n: usize) -> Take<Self>

the self as first argument tells me that take() must be used on an owned value, not a reference (&self) or mutable reference (&mut self).

The documentation for by_ref() says it returns a mutable reference, and

This is useful to allow applying iterator adaptors while still retaining ownership of the original iterator.

It includes an example using by_ref().take(n), and then continuing to use the iterator (just like I wanted).

My question is, why am I allowed to call take() on a mutable reference (as opposed to requiring an owned value)? In other words, shouldn't take() have been declared something like:

fn take(&mut self, n: usize) -> Take<Self>

to make it possible to use it with a mutable reference?

What declaration should I have been looking for that would have told me this was possible?


Solution

  • Rust treats T, &T, and &mut T as separate types. Being separate types we can implement different methods for each of them. Example:

    struct Struct;
    
    trait Printable {
        fn print(self);
    }
    
    impl Printable for Struct {
        fn print(self) {
            println!("I'm an owned Struct");
        }
    }
    
    impl Printable for &Struct {
        fn print(self) {
            println!("I'm a Struct reference");
        }
    }
    
    fn main() {
        let s = Struct;
        let s_ref = &Struct;
        s.print();
        s_ref.print();
    }
    

    If we desugar the trait method we get:

    fn print(self: Self) { /* implementation */ }
    

    Where Self is equal to the implementing type, which in the case of Struct is:

    fn print(self: Struct) { /* implementation */ }
    

    And in the case of &Struct is:

    fn print(self: &Struct) { /* implementation */ }
    

    So really self can be an owned type or an immutable reference or a mutable reference. The reason why you can call take on mutable references to Iterators is because of this generic blanket impl which implements the Iterator trait on all mutable references to Iterators:

    impl<'_, I> Iterator for &'_ mut I where I: Iterator + ?Sized { /* implementation */ }
    

    Let's use vec.into_iter() as a concrete example. Since it returns std::vec::IntoIter which implements Iterator we know this implementation of take must exist:

    take(self: std::vec::IntoIter, n: usize) -> Take<std::vec::IntoIter> { /* implementation */ }
    

    However, we also know that an implementation of take for &mut std::vec::IntoIter must exist because it would be automatically generated by the generic blanket impl mentioned above, and that implementation's signature would look like this:

    take(self: &mut std::vec::IntoIter, n: usize) -> Take<&mut std::vec::IntoIter> { /* implementation */ }
    

    And this why you can call take on any mutable reference to any type that implements Iterator.