这里有两个功能:
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);
}
第一个编译正常,但第二个编译错误:
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
by_ref
和take
的特征在std::iter::Iterator
和std::io::Read
的特征上几乎相同,因此我认为如果第一个进行编译,第二个也将进行编译。我在哪里弄错了?
答案 0 :(得分:8)
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I
是第一个函数编译的原因。它为所有对迭代器的可变引用实现Iterator
。
Read
特质has the equivalent,但与Iterator
不同的是,Read
特质不在the prelude中,因此您需要{{ 1}}使用此隐含功能:
use std::io::Read
答案 1 :(得分:4)
这确实是一个令人困惑的错误消息,并且得到它的原因相当微妙。 answer by ozkriff正确地解释了这是因为Read
特性不在范围内。我想添加更多上下文,并解释为什么会看到特定的错误,而不是找不到该方法的错误。
take()
和Read
上的Iterator
方法按值取self
,换句话说,它消耗了接收者。这意味着只有拥有接收者的所有权才能调用它。您问题中的函数通过可变引用接受iter
,因此它们不拥有基础I
对象,因此您不能为基础调用<Iterator>::take()
或<Read>::take()
对象。
但是,正如ozkriff指出的那样,标准库提供了Iterator
和Read
的“转发”实现,以可变地引用实现各自特征的类型。当您在第一个函数中调用iter.take(2)
时,实际上实际上是调用了<&mut Iterator<Item = T>>::take(iter, 2)
,它仅消耗对迭代器的可变引用,而不消耗迭代器本身。这是完全正确的;尽管该函数不拥有迭代器本身,因为它不拥有该迭代器,但该函数却拥有引用。但是,在第二个函数中,您最终要调用<Read>::take(*iter, 2)
,它会尝试消耗底层的读取器。由于您不拥有该阅读器,因此会收到一条错误消息,说明您无法将其移出借用的上下文。
那么为什么第二个方法调用解析为另一个方法? ozkriff的答案已经解释了发生这种情况是因为Iterator
特性在标准序言中,而Read
特性在默认情况下不在范围内。让我们更详细地研究方法查找。在Rust语言参考的"Method call expressions"部分中对此进行了记录:
第一步是建立候选接收者类型的列表。通过重复引用接收方表达式的类型,将遇到的每种类型添加到列表中,然后最后尝试在结尾处尝试不定大小的强制,然后添加结果类型(如果成功)来获取这些内容。然后,对于每个候选人
T
,在&T
之后立即将&mut T
和T
添加到列表中。
根据此规则,我们的候选类型列表为
&mut I, &&mut I, &mut &mut I, I, &I, &mut I
然后,对于每种候选类型
T
,在以下位置使用该类型的接收器搜索可见的方法:
T
的固有方法(直接在T
上实现的方法)。由
T
实现的可见特征提供的任何方法。如果T
是类型参数,则首先查找T
上的特征范围提供的方法。然后查找范围内的所有其余方法。
对于I: Iterator
,此过程开始于在take()
上查找&mut I
方法。 &mut I
上没有固有的方法,因为I
是泛型类型,因此我们可以跳过步骤1。在步骤2中,我们首先在&mut I
的特征范围上查找方法,但是I
仅存在特征边界,因此我们继续在范围内的所有其余方法上查找take()
。由于Iterator
在范围内,因此我们确实可以从标准库中找到转发实现,并且可以停止处理我们的候选类型列表。
对于第二种情况I: Read
,我们也从&mut I
开始,但是由于Read
不在范围内,因此我们看不到转发实现。但是,一旦到达候选类型列表中的I
,就会启动有关特征范围提供的方法的子句:无论特征是否在范围内,都将首先查找它们。 I
的特征范围为Read
,因此找到了<Read>::take()
。如上所述,调用此方法会导致错误消息。
总而言之,特征必须在范围内才能使用其方法,但是即使特征不在范围内,也可以使用基于特征范围的方法。