为什么 by_ref().take() 的用法在 Iterator 和 Read 特征之间有所不同?
Why does the usage of by_ref().take() differ between the Iterator and Read traits?
这里有两个函数:
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
特征中几乎相同,所以我假设如果第一个编译,第二个也会编译也。我错在哪里了?
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I
是第一个函数编译的原因。它为迭代器的所有可变引用实现 Iterator
。
Read
特征 has the equivalent, but, unlike Iterator
, the Read
trait isn't in the prelude,因此您需要 use std::io::Read
才能使用此实现:
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);
}
这确实是一条令人困惑的错误消息,您收到它的原因相当微妙。 正确地解释了这是因为 Read
特征不在范围内。我想添加更多上下文,并解释为什么您会收到您看到的特定错误,而不是找不到方法的错误。
Read
和 Iterator
上的 take()
方法按值获取 self
,换句话说,它会消耗其接收者。这意味着您只有拥有接收者的所有权才能调用它。您问题中的函数通过可变引用接受 iter
,因此它们不拥有底层 I
对象,因此您不能为底层调用 <Iterator>::take()
或 <Read>::take()
对象。
然而,正如 ozkriff 所指出的,标准库提供了 "forwarding" Iterator
和 Read
的实现,用于对实现各自特征的类型的可变引用。当你在你的第一个函数中调用 iter.take(2)
时,你实际上最终调用了 <&mut Iterator<Item = T>>::take(iter, 2)
,它只消耗你对迭代器的可变引用,而不是迭代器本身。这是完全正确的;虽然函数不能使用迭代器本身,因为它不拥有迭代器,但函数确实拥有引用。然而,在第二个函数中,您最终调用了 <Read>::take(*iter, 2)
,它试图消耗底层的 reader。由于您不拥有那个 reader,您会收到一条错误消息,说明您无法将其移出借用的上下文。
那么为什么第二个方法调用解析为不同的方法? ozkriff 的回答已经解释了发生这种情况是因为 Iterator
特征在标准前奏中,而 Read
特征默认不在范围内。让我们更详细地看一下方法查找。它记录在 Rust 语言参考的 "Method call expressions" 部分:
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 after T
.
根据这个规则,我们的候选类型列表是
&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 on T
).
Any of the methods provided by a visible trait implemented by T
. If T
is a type parameter, methods provided by trait bounds on T
are looked up first. Then all remaining methods in scope are looked up.
对于案例 I: Iterator
,此过程从查找 &mut I
上的 take()
方法开始。 &mut I
上没有固有方法,因为 I
是泛型类型,所以我们可以跳过步骤 1。在步骤 2 中,我们首先查找 &mut I
的特征边界上的方法,但是I
只有特征边界,所以我们继续查找范围内所有剩余方法的 take()
。由于 Iterator
在范围内,我们确实从标准库中找到了转发实现,并且可以停止处理我们的候选类型列表。
对于第二种情况,I: Read
,我们也从&mut I
开始,但是由于Read
不在范围内,我们不会看到转发实现。但是,一旦我们到达候选类型列表中的 I
,特征边界提供的方法的子句就会生效:首先查找它们,无论特征是否在范围内。 I
的特征界限为 Read
,因此找到了 <Read>::take()
。正如我们在上面看到的,调用此方法会导致错误消息。
总而言之,特征必须在范围内才能使用它们的方法,但即使特征不在范围内,也可以使用特征边界上的方法。
这里有两个函数:
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
特征中几乎相同,所以我假设如果第一个编译,第二个也会编译也。我错在哪里了?
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I
是第一个函数编译的原因。它为迭代器的所有可变引用实现 Iterator
。
Read
特征 has the equivalent, but, unlike Iterator
, the Read
trait isn't in the prelude,因此您需要 use std::io::Read
才能使用此实现:
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);
}
这确实是一条令人困惑的错误消息,您收到它的原因相当微妙。 Read
特征不在范围内。我想添加更多上下文,并解释为什么您会收到您看到的特定错误,而不是找不到方法的错误。
Read
和 Iterator
上的 take()
方法按值获取 self
,换句话说,它会消耗其接收者。这意味着您只有拥有接收者的所有权才能调用它。您问题中的函数通过可变引用接受 iter
,因此它们不拥有底层 I
对象,因此您不能为底层调用 <Iterator>::take()
或 <Read>::take()
对象。
然而,正如 ozkriff 所指出的,标准库提供了 "forwarding" Iterator
和 Read
的实现,用于对实现各自特征的类型的可变引用。当你在你的第一个函数中调用 iter.take(2)
时,你实际上最终调用了 <&mut Iterator<Item = T>>::take(iter, 2)
,它只消耗你对迭代器的可变引用,而不是迭代器本身。这是完全正确的;虽然函数不能使用迭代器本身,因为它不拥有迭代器,但函数确实拥有引用。然而,在第二个函数中,您最终调用了 <Read>::take(*iter, 2)
,它试图消耗底层的 reader。由于您不拥有那个 reader,您会收到一条错误消息,说明您无法将其移出借用的上下文。
那么为什么第二个方法调用解析为不同的方法? ozkriff 的回答已经解释了发生这种情况是因为 Iterator
特征在标准前奏中,而 Read
特征默认不在范围内。让我们更详细地看一下方法查找。它记录在 Rust 语言参考的 "Method call expressions" 部分:
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
.
根据这个规则,我们的候选类型列表是
&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.
对于案例 I: Iterator
,此过程从查找 &mut I
上的 take()
方法开始。 &mut I
上没有固有方法,因为 I
是泛型类型,所以我们可以跳过步骤 1。在步骤 2 中,我们首先查找 &mut I
的特征边界上的方法,但是I
只有特征边界,所以我们继续查找范围内所有剩余方法的 take()
。由于 Iterator
在范围内,我们确实从标准库中找到了转发实现,并且可以停止处理我们的候选类型列表。
对于第二种情况,I: Read
,我们也从&mut I
开始,但是由于Read
不在范围内,我们不会看到转发实现。但是,一旦我们到达候选类型列表中的 I
,特征边界提供的方法的子句就会生效:首先查找它们,无论特征是否在范围内。 I
的特征界限为 Read
,因此找到了 <Read>::take()
。正如我们在上面看到的,调用此方法会导致错误消息。
总而言之,特征必须在范围内才能使用它们的方法,但即使特征不在范围内,也可以使用特征边界上的方法。