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 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 afterT
.
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:
T
's inherent methods (methods implemented directly onT
).Any of the methods provided by a visible trait implemented by
T
. IfT
is a type parameter, methods provided by trait bounds onT
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With