这确实是一个令人困惑的错误信息,你之所以会得到这个错误是因为原因相当微妙。ozkriff的
答案正确地解释了这是因为
Read
特质不在作用域内。我想添加一些更多的上下文和解释,为什么你会看到特定的错误,而不是方法未找到的错误。
Read
和
Iterator
上的
take()
方法通过值获取
self
,换句话说,它消耗了它的接收器。这意味着只有当你拥有接收器时才能调用它。你问题中的函数通过可变引用接受
iter
,因此它们不拥有底层的
I
对象,所以你不能为底层对象调用
<Iterator>::take()
或
<Read>::take()
。
然而,正如ozkriff指出的那样,标准库为实现相应特质的类型提供了对可变引用的“转发”实现。当你在第一个函数中调用
iter.take(2)
时,实际上会调用
<&mut Iterator<Item = T>>::take(iter, 2)
,它只消耗了你对迭代器的可变引用,而不是迭代器本身。这是完全有效的;虽然函数不能消耗迭代器本身,因为它没有拥有它,但函数确实拥有引用。然而,在第二个函数中,你最终调用了
<Read>::take(*iter, 2)
,它试图消耗底层读取器。由于你没有拥有那个读取器,你会得到一个错误消息,说明你无法将其移动出借用上下文。
那么为什么第二个方法调用解析为不同的方法呢?ozkriff的答案已经解释了这是因为
Iterator
特质在标准预导模块中,而
Read
特质默认情况下不在作用域内。让我们更详细地看一下方法查找。这在Rust语言参考手册的
“方法调用表达式”部分有记录:
第一步是构建接收方类型的候选列表。通过重复解引用接收方表达式的类型,将遇到的每种类型添加到列表中,然后最后尝试进行非大小限制的强制转换,如果成功,则添加结果类型。然后,对于每个候选项T
,立即在T
之后添加&T
和&mut T
。
根据这个规则,我们的候选类型列表是:
&mut I, &&mut I, &mut &mut I, I, &I, &mut I
然后,对于每个候选类型T,在以下位置搜索具有该类型接收器的可见方法:
1. T的内在方法(直接在T上实现的方法)。
2. 由T实现的任何可见特征提供的方法。如果T是类型参数,则首先查找T上的特征边界提供的方法。然后查找所有剩余的作用域中的方法。
对于的情况,这个过程从在&mut I上查找take()方法开始。由于I是一个泛型类型,所以&mut I上没有内在方法,因此我们可以跳过步骤1。在步骤2中,我们首先查找&mut I的特征边界上的方法,但只有I的特征边界,所以我们继续查找作用域中所有剩余的方法来查找take()。由于Iterator在作用域中,我们确实找到了标准库中的转发实现,并且可以停止处理我们的候选类型列表。
对于第二种情况,,我们也从&mut I开始,但由于Read不在作用域中,我们将看不到转发实现。然而,一旦我们到达候选类型列表中的I,特征边界提供的方法子句就会生效:无论特征是否在作用域中,它们都会首先被查找。I具有Read的特征边界,因此找到了::take()。正如我们上面所看到的,调用此方法会导致错误消息。
总之,要使用其方法,必须将特征包含在作用域中,但是即使特征不在作用域中,也可以使用特征边界上的方法。
Read
的等价物。但是,与Iterator
不同,Read
特质没有预导模块,所以您需要使用use std::io::Read
来使用此实现。 - kennytm