The same name in the iterator

see the code.


impl<T> IntoIterator for LinkedList<T> {
    type Item = T;
    type IntoIter = IntoIter<T>;

    fn into_iter(self) -> Self::IntoIter {
        self.into_iter()
    }
}

impl<'a, T> IntoIterator for &'a LinkedList<T> {
    type Item = &'a T;
    type IntoIter = Iter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

impl<'a, T> IntoIterator for &'a mut LinkedList<T> {
    type Item = &'a mut T;
    type IntoIter = IterMut<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter_mut()
    }
}
    #[test]
    fn test_base_first() {
        let mut list: LinkedList<i32> = LinkedList::new();

        let it1: Iter<i32> = (&list).into_iter();
        let it2: IterMut<i32> = (&mut list).into_iter();
        let it3: IntoIter<i32> = list.into_iter();
  }

LinkedList implements IntoIterator 3 times, so it has 3 'into_iter' methods with the same name, and when I call it with different variations of list, I can call different methods. The principle of this confuses me.

ๅฎŒๆ•ด็š„ไปฃ็ ๅ‚่€ƒ่ฟ™้‡Œ๏ผšBoring Combinatorics - Learning Rust With Entirely Too Many Linked Lists

It's a bit confusing, but this is what lets you iterate over collections by value or using different types of references.

For example,

let mut numbers: Vec<u32> = vec![1, 2, 3, 4];

for number in &numbers {
  // uses "impl IntoIterator for &Vec<u8>"
  // number is a &u32 here
}

for number in &mut numbers {
  // uses "impl IntoIterator for &mut Vec<u8>"
  // number is a &mut u32
}

for number in numbers {
  // uses "impl IntoIterator for Vec<u8>"
  // number is a u32
  // the original numbers vector is no longer available
}

You'll see this pattern quite often when it comes to collections.

References like &Vec<u8> are their own types, so it's perfectly fine to implement a trait for a &T and a T at the same time.

2 Likes

What does the lifecycle identifier declared between for and struct type do? What is this syntax?
Is this declaration the reason why I can call the same method name with different variations and actually call to different implementations?

In addition to the examples @Michael-F-Bryan provided, it's typical for collections to provide methods with distinct names which yield those same iterators, for example if you own a LinkedList then

  • list.into_iter() will give you the owned iterator (over T) via the trait
  • list.iter_mut() will give you the exclusive borrow iterator (over &mut T) via a method
  • list.iter() will give you the shared borrow iterator (over &T) via a method

The methods are more idiomatic and ergonomic in expression position than, say, (&list).into_iter(). Where as was demonstrated above, for item in &list is the more ergnomic (and idiomatic) approach when using for. (What the pattern means is something you pick up when using Rust; you'll see it relatively frequently.)


&list creates a shared reference to the list and &mut list creates an exclusive reference to the list. They're similar to address-of operators. The reference calls them borrow operators.

This can happen automatically as part of method calls. In my bullet points above, list.iter() creates a &list to pass to LinkedList::iter and list.iter_mut() analogously creates a &mut list.

The next question is, how did the compiler choose which methods to call?


To understand what methods are called in which situations and why it's unambiguous, you'll have to learn about method call resolution. The pertinent point when calling .into_iter() is that if the type of the variable which is the operand of the method call (to the left of the .) is a receiver of the method, that's the 'winner" of method call resolution. So

  • list.into_iter() "sees" the implementation for the owned LinkedList first
  • (&list).into_iter() "sees" the implementation for &LinkedList first
  • (&mut list).into_iter() "sees" the implementation for &mut LinkedList first

for ultimately calls Intoiterator::into_iterator on the in _ expression and works in the analogous fashion.

Whereas with list.iter(), there is no method called iter with a receiver of type LinkedList, so the search continues. There is a method called iter with a receiver of type &LinkedList, and that's the winner.

list.iter_mut() similarly finds no LinkedList receiver method, also finds no &LinkedList receiver method, but then does find the &mut LinkedList receiver method called iter_mut, so that's the winner.

2 Likes

Thank you for your answer.
Another thing that puzzles me is that both of the following impls are wrong

impl<T> &LinkedList<T> {
 // cannot define inherent `impl` for primitive type
 // = help: consider using an extension trait instead
 // = note: you could also try moving the reference to uses of `seven::LinkedList<T>` (such as `self`) within the implementation
}

impl<'a, T> &'a LinkedList<T> {
}

It seems that preceding the struct type with a reference and a lifecycle identifier must be implemented in the trait.

Yep. This comes back to the "references are types, too" thing I said. You can think of &T as being a type implemented by the standard library. In the same way that I can't add extra inherent methods to std::string::String, you can't add extra methods to a reference to LinkedList<T>.

2 Likes

The rules for so-called "inherent" impls and trait impls are different. In either case we have restrictions what the Self-type, i. e. the type you write at the end of the impl line before the opening {-brace, but these restrictions are not the same.

For the full story, see this page in the reference: Implementations - The Rust Reference

In short, the Self-type for an inherent impl must always be a type that is defined in the same crate as the impl. So it's always a struct or enum or union defined in the current crate, or a dyn Trait + โ€ฆ trait object type for a trait that is defined in the same crate.

The rules for trait impls are a lot more complex, but for the common case of simple traits without any additional type arguments (like trait Foo {} but unlike trait Bar<T> {}), it boils down to, essentially, the rule that either

  • the trait is defined in the same crate, in which case there are no restrictions to the Self-type at all. Since traits can define methods (callable using method call syntax), this rule explains the "extension trait" pattern that is commonly used in crates to implement extra methods for types from other crates, the standard library, or built-in ones. Or
  • the trait is defined in a different crate, in which case the Self type must be defined in the same crate or of the form &T, &mut T, Box<T> or Pin<T> where T is a permissable Self-type. This applies recursively, so type like Pin<Box<MyStruct>>, will work, too.

As you can see, this allows for trait implementations for type like &LinkedList<T> (in the same trait where LinkedList is defined) but not for inherent impls on the same type, as those didn't have the relevant exception. Usually thats not too much of a problem, as the receiver type of a method can still be &LinkedList<T> by means of defining a &self method, whereas for trait implementations, the trait itself may prescribe a self argument, like IntoIterator::into_iter does, which is why it's more useful for trait impls to be less restrictive here.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.