为什么Iterator和Read特征之间by_ref()。take()的用法不同?

时间:2018-09-26 10:17:01

标签: rust borrow-checker

这里有两个功能:

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_reftake的特征在std::iter::Iteratorstd::io::Read的特征上几乎相同,因此我认为如果第一个进行编译,第二个也将进行编译。我在哪里弄错了?

2 个答案:

答案 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

Playground

答案 1 :(得分:4)

这确实是一个令人困惑的错误消息,并且得到它的原因相当微妙。 answer by ozkriff正确地解释了这是因为Read特性不在范围内。我想添加更多上下文,并解释为什么会看到特定的错误,而不是找不到该方法的错误。

take()Read上的Iterator方法按值取self,换句话说,它消耗了接收者。这意味着只有拥有接收者的所有权才能调用它。您问题中的函数通过可变引用接受iter,因此它们不拥有基础I对象,因此您不能为基础调用<Iterator>::take()<Read>::take()对象。

但是,正如ozkriff指出的那样,标准库提供了IteratorRead的“转发”实现,以可变地引用实现各自特征的类型。当您在第一个函数中调用iter.take(2)时,实际上实际上是调用了<&mut Iterator<Item = T>>::take(iter, 2),它仅消耗对迭代器的可变引用,而不消耗迭代器本身。这是完全正确的;尽管该函数不拥有迭代器本身,因为它不拥有该迭代器,但该函数却拥有引用。但是,在第二个函数中,您最终要调用<Read>::take(*iter, 2),它会尝试消耗底层的读取器。由于您不拥有该阅读器,因此会收到一条错误消息,说明您无法将其移出借用的上下文。

那么为什么第二个方法调用解析为另一个方法? ozkriff的答案已经解释了发生这种情况是因为Iterator特性在标准序言中,而Read特性在默认情况下不在范围内。让我们更详细地研究方法查找。在Rust语言参考的"Method call expressions"部分中对此进行了记录:

  

第一步是建立候选接收者类型的列表。通过重复引用接收方表达式的类型,将遇到的每种类型添加到列表中,然后最后尝试在结尾处尝试不定大小的强制,然后添加结果类型(如果成功)来获取这些内容。然后,对于每个候选人T,在&T之后立即将&mut TT添加到列表中。

根据此规则,我们的候选类型列表为

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

然后,对于每种候选类型T,在以下位置使用该类型的接收器搜索可见的方法:

     
      
  1. T的固有方法(直接在T上实现的方法)。

  2.   
  3. T实现的可见特征提供的任何方法。如果T是类型参数,则首先查找T上的特征范围提供的方法。然后查找范围内的所有其余方法。

  4.   

对于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()。如上所述,调用此方法会导致错误消息。

总而言之,特征必须在范围内才能使用其方法,但是即使特征不在范围内,也可以使用基于特征范围的方法。