Ole*_*Cat 9 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);
}
Run Code Online (Sandbox Code Playgroud)
虽然第一个编译正常,第二个编译错误:
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
Run Code Online (Sandbox Code Playgroud)
签名by_ref和take几乎完全相同std::iter::Iterator和std::io::Read特征,所以我认为如果第一个编译,第二个也将编译.我哪里弄错了?
Sve*_*ach 10
这确实是一个令人困惑的错误消息,您得到它的原因相当微妙。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 的回答已经解释了发生这种情况是因为Iteratortrait 在标准前奏中,而Readtrait 默认不在范围内。让我们更详细地看一下方法查找。它记录在Rust 语言参考的“方法调用表达式”部分:
第一步是构建候选接收器类型列表。通过反复取消引用接收者表达式的类型,将遇到的每个类型添加到列表中,然后最后尝试进行无大小强制转换,如果成功,则添加结果类型来获得这些。然后,对于每个候选者
T,在 之后立即将&T和添加&mut T到列表中T。
根据这个规则,我们的候选类型列表是
&mut I, &&mut I, &mut &mut I, I, &I, &mut I
Run Code Online (Sandbox Code Playgroud)
然后,对于每个候选类型
T,在以下位置搜索具有该类型接收器的可见方法:
T的固有方法(直接在 上实现的方法T)。由 实现的可见特征提供的任何方法
T。如果T是类型参数,T则首先查找trait bounds on 提供的方法。然后查找范围内的所有剩余方法。
对于这种情况I: Iterator,此过程从查找take()方法开始&mut I。上有没有内在的方法&mut I,因为I是一个通用型的,所以我们可以跳过步骤1在步骤2中,我们先来看看了对性状界的方法&mut I,但也有仅特质界I,所以我们移动到查找take()上范围内的所有剩余方法。由于Iterator在范围内,我们确实从标准库中找到了转发实现,并且可以停止处理我们的候选类型列表。
对于第二种情况,I: Read我们也从 开始&mut I,但由于Read不在范围内,我们不会看到转发实现。但是,一旦我们进入I候选类型列表,关于 trait bounds 提供的方法的条款就开始了:首先查找它们,无论 trait 是否在范围内。 I有一个特征界限Read,所以<Read>::take()找到了。正如我们在上面看到的,调用此方法会导致错误消息。
总之,特征必须在范围内才能使用它们的方法,但即使特征不在范围内,也可以使用特征边界上的方法。
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I是第一个函数编译的原因.它实现Iterator了对迭代器的所有可变引用.
该Read特征具有相当的,但不像Iterator的Read特点是没有前奏,所以你需要use std::io::Read使用这个IMPL:
use std::io::Read; // remove this to get "cannot move out of borrowed content" err
fn foo<I, T>(iter: &mut I)
where
I: std::iter::Iterator<Item = T>,
{
let _y = iter.take(2);
}
fn bar<I>(iter: &mut I)
where
I: std::io::Read,
{
let _y = iter.take(2);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
258 次 |
| 最近记录: |