17

Here are two functions:

fn foo<I>(iter: &mut I)
where
    I: std::iter::Iterator<Item = u8>,
{
    let x = iter.by_ref();
    let y = x.take(2);
}

fn bar<I>(iter: &mut I)
where
    I: std::io::Read,
{
    let x = iter.by_ref();
    let y = x.take(2);
}

While the first compiles fine, the second gives the compilation error:

error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:14:13
   |
14 |     let y = x.take(2);
   |             ^ cannot move out of borrowed content

The signatures of by_ref and take are almost identical in std::iter::Iterator and std::io::Read traits, so I supposed that if the first one compiles, the second will compile too. Where am I mistaken?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
OlegTheCat
  • 4,443
  • 16
  • 24

2 Answers2

14

This is indeed a confusing error message, and the reason you get it is rather subtle. The answer by ozkriff correctly explains that this is because the Read trait is not in scope. I'd like to add a bit more context, and an explanation why you are getting the specific error you see, rather than an error that the method wasn't found.

The take() method on Read and Iterator takes self by value, or in other words, it consumes its receiver. This means you can only call it if you have ownership of the receiver. The functions in your question accept iter by mutable reference, so they don't own the underlying I object, so you can't call <Iterator>::take() or <Read>::take() for the underlying object.

However, as pointed out by ozkriff, the standard library provides "forwarding" implementations of Iterator and Read for mutable references to types that implement the respective traits. When you call iter.take(2) in your first function, you actually end up calling <&mut Iterator<Item = T>>::take(iter, 2), which only consumes your mutable reference to the iterator, not the iterator itself. This is perfectly valid; while the function can't consume the iterator itself since it does not own it, the function does own the reference. In the second function, however, you end up calling <Read>::take(*iter, 2), which tries to consume the underlying reader. Since you don't own that reader, you get an error message explaining that you can't move it out of the borrowed context.

So why does the second method call resolve to a different method? The answer by ozkriff already explains that this happens because the Iterator trait is in the standard prelude, while the Read trait isn't in scope by default. Let's look at the method lookup in more detail. It is documented in the section "Method call expressions" of the Rust language reference:

The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate T, add &T and &mut T to the list immediately after T.

According to this rule, our list of candidate types is

&mut I, &&mut I, &mut &mut I, I, &I, &mut I

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

  1. T's inherent methods (methods implemented directly on T).

  2. Any of the methods provided by a visible trait implemented by T. If T is a type parameter, methods provided by trait bounds on T are looked up first. Then all remaining methods in scope are looked up.

For the case I: Iterator, this process starts with looking up a take() method on &mut I. There are no inherent methods on &mut I, since I is a generic type, so we can skip step 1. In step 2, we first look up methods on trait bounds for &mut I, but there are only trait bounds for I, so we move on to looking up take() on all remaining methods in scope. Since Iterator is in scope, we indeed find the forwarding implementation from the standard library, and can stop processing our list of candidate types.

For the second case, I: Read, we also start with &mut I, but since Read is not in scope, we won't see the forwarding implementation. Once we get to I in our list of candidate types, though, the clause on methods provided by trait bounds kicks in: they are looked up first, regardless of whether the trait is in scope. I has a trait bound of Read, so <Read>::take() is found. As we have seen above, calling this method causes the error message.

In summary, traits must be in scope to use their methods, but methods on trait bounds can be used even if the trait isn't in scope.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • I can't really understand this: "you actually end up calling `<&mut Iterator>::take(iter, 2)`, which *only consumes your mutable reference* to the iterator"... How come a `self` parameter ends up being actually an `&mut self`? So when we see a method anywhere that seems to require ownership, it may not be the case?? – rsalmei Aug 04 '21 at 20:57
  • 1
    @rsalmei The type of `self` always depends on the type you are calling the method on. If that type is a mutable reference, and there's an implementation of `Iterator` on that mutable reference type, then yes, taking `self` by value means taking a mutable reference. – Sven Marnach Aug 04 '21 at 21:11
  • 2
    In other words, if the reciever parameter in the trait is written as `self`, then it will always have type `Self`, but `Self` is allowed to be a mutable reference type, provided we explicitly implement the trait on that mutable reference type. And the forwarding implementation for `&mut Iterator` does exactly that – it implements the `Iterator` trait for any mutable reference to a type implementing the `Iterator` trait. – Sven Marnach Aug 04 '21 at 21:12
  • Thanks Sven! I think I get it, that would explain why in the [core &mut I implementation](https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#3440-3454) they have to dereference it twice "`(**self)`"! Very nice. – rsalmei Aug 04 '21 at 21:47
  • 2
    @rsalmei Exactly – derefencing only once would lead to infinte recursion. – Sven Marnach Aug 05 '21 at 06:41
  • Great explanation. Can I ask why many iterator methods like take and count take self and consume iterator by default when &mut self option is available? – raj Sep 13 '21 at 21:29
  • @raj For iterator adaptors like `take()`, it just feels more natural to me, since otherwise you'd end up with crazily nested references for long method call chains. The newly returned iterator would always contain a mutable reference to the original iterator, and the new iterator would be borrowed mutably again for the next method call. Possibly it's hard for the optimizer to untangle this level of indirection. For `.count()`, the reason simply is that it consumes the whole iterator anyway, so it's not useful to keep it around. – Sven Marnach Sep 14 '21 at 11:26
13

impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I is the reason why the first function compiles. It implements Iterator for all mutable references to iterators.

The Read trait has the equivalent, but, unlike Iterator, the Read trait isn't in the prelude, so you'll need to use std::io::Read to use this impl:

use std::io::Read; // remove this to get "cannot move out of borrowed content" err

fn foo<I, T>(iter: &mut I)
where
    I: std::iter::Iterator<Item = T>,
{
    let _y = iter.take(2);
}

fn bar<I>(iter: &mut I)
where
    I: std::io::Read,
{
    let _y = iter.take(2);
}

Playground

ozkriff
  • 1,269
  • 15
  • 23
  • 7
    We do have [the equivalent](https://doc.rust-lang.org/1.29.1/src/std/io/impls.rs.html#20-45) for `Read`. However, unlike `Iterator`, the `Read` trait isn't in prelude, so you'll need to `use std::io::Read` to use this impl. – kennytm Sep 26 '18 at 11:55
  • Please explain why OP gets the specific error they do before `Read` is in scope. – Shepmaster Sep 26 '18 at 15:03
  • 2
    @Shepmaster I added a somewhat lengthy explanation in a separate answer. I'm sure you know the reason, but anyway, you asked. :) – Sven Marnach Sep 27 '18 at 11:44