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?
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 onRead
andIterator
takesself
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 acceptiter
by mutable reference, so they don't own the underlyingI
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
andRead
for mutable references to types that implement the respective traits. When you calliter.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 theRead
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:According to this rule, our list of candidate types is
For the case
I: Iterator
, this process starts with looking up atake()
method on&mut I
. There are no inherent methods on&mut I
, sinceI
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 forI
, so we move on to looking uptake()
on all remaining methods in scope. SinceIterator
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 sinceRead
is not in scope, we won't see the forwarding implementation. Once we get toI
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 ofRead
, 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.
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I
is the reason why the first function compiles. It implementsIterator
for all mutable references to iterators.The
Read
trait has the equivalent, but, unlikeIterator
, theRead
trait isn't in the prelude, so you'll need touse std::io::Read
to use this impl:Playground