rust - 为什么将函数移动到默认特征方法会引入借用错误?
问题描述
给定Foo
包含元素集合的结构:
#[derive(Debug)]
struct Foo {
bar: Vec<i8>,
}
我编写了一个可变视图对象,旨在封装以下内容的一部分Foo
:
#[derive(Debug)]
struct View<'a> {
foo: &'a mut Foo,
}
impl<'a> View<'a> {
fn iter(&'a self) -> std::slice::Iter<'a, i8> {
self.foo.bar.iter()
}
fn iter_mut(&'a mut self) -> std::slice::IterMut<'a, i8> {
self.foo.bar.iter_mut()
}
fn mutate(&'a mut self) {
let mut vector: Vec<i8> = vec![];
for value in self.iter().take(1).cloned() {
vector.push(value);
}
for value in self.iter_mut() {
*value = 0;
}
}
}
上面的View
结构按预期工作,并打印以下代码Foo { bar: [0, 0, 0] }
。
fn main() {
let mut foo = Foo { bar: vec![0, 1, 2] };
let mut view = View { foo: &mut foo };
view.mutate();
println!("{:?}", foo);
}
然而,不同类型的视图应该是可能的——如果Foo
是矩阵,视图可以是行、列,甚至是子矩阵。因此,我将 重写View
为由结构实现的特征,并给出mutate
了默认实现:
trait AbstractView<'a> {
type Iterator: Iterator<Item = &'a i8>;
type IteratorMut: Iterator<Item = &'a mut i8>;
fn iter(&'a self) -> Self::Iterator;
fn iter_mut(&'a mut self) -> Self::IteratorMut;
fn mutate(&'a mut self) {
let mut vector: Vec<i8> = vec![];
for value in self.iter().take(1).cloned() {
vector.push(value);
}
for value in self.iter_mut() {
*value = vector[0];
}
}
}
#[derive(Debug)]
struct View<'a> {
foo: &'a mut Foo,
}
impl<'a> AbstractView<'a> for View<'a> {
type Iterator = std::slice::Iter<'a, i8>;
type IteratorMut = std::slice::IterMut<'a, i8>;
fn iter(&'a self) -> Self::Iterator {
self.foo.bar.iter()
}
fn iter_mut(&'a mut self) -> Self::IteratorMut {
self.foo.bar.iter_mut()
}
}
此代码没有成功编译,rustc 抱怨对iter_mut
in的调用mutate
:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:18:22
|
6 | trait AbstractView<'a> {
| -- lifetime `'a` defined here
...
15 | for value in self.iter().take(1).cloned() {
| -----------
| |
| immutable borrow occurs here
| argument requires that `*self` is borrowed for `'a`
...
18 | for value in self.iter_mut() {
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
为什么mutate
在 trait 上实现为默认方法会导致看起来与借用检查器不同的行为?我怎样才能让这个特性发挥作用?
使用 rustc 版本 1.43.1。
解决方案
很容易说为什么基于特征的版本不起作用,但很难说为什么原来的版本起作用。
这一切都在有生之年。对于基于特征的版本,到处只有一个生命周期'a
。当我们调用self.iter()
orself.iter_mut()
时,借用会持续相同的生命周期。这意味着我们不能同时调用两者:如果我们调用两者,不可变借用和可变借用具有相同的生命周期,因此它们同时存在。
这就提出了为什么非特征版本有效的问题。它不做完全相同的事情吗?答案在于类型和的差异。泛型类型的变体是是否可以强制以及如何强制到何时和相关。std::slice::Iter<'a, T>
std::slice::IterMut<'a, T>
T<'a>
T<'a>
T<'b>
'a
'b
对于许多类型,这种关系是协变的:如果'a
比'b
(书面'a: 'b
)更长,那么 type 的值T<'a>
可以强制转换为 type 的值T<'b>
。对于其他一些类型,这种关系是逆变的: if 'a: 'b
, thenT<'b>
可以被强制转换为T<'a>
(这方面的一个例子是Fn(&'a T)
)。最后,某些类型是不变的,因此不会发生强制。
std::slice::Iter<'a, T>
在生命周期内是协变的'a
。如果'a
比 长'b
,我们可以强制到更短的生命周期。这正是您的代码中发生的事情。当我们调用时self.iter().take(1).cloned()
,self.iter()
实际上是强制转换为更短std::slice::Iter<'b, i8>
的,以便可变借用可以在以后发生。
fn mutate(&'a mut self) {
let mut vector: Vec<i8> = vec![];
// let iter = self.iter(); // works
let mut iter: std::slice::Iter<'a, i8> = self.iter(); // doesn't work!
for value in iter.take(1).cloned() {
vector.push(value);
}
for value in self.iter_mut() {
*value = vector[0];
}
}
使用上面的代码,我们会得到一个类似于基于特征的代码的错误。
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:27:22
|
11 | impl<'a> View<'a> {
| -- lifetime `'a` defined here
...
23 | let iter: std::slice::Iter<'a, i8> = self.iter(); // doesn't work!
| ------------------------ ---- immutable borrow occurs here
| |
| type annotation requires that `*self` is borrowed for `'a`
...
27 | for value in self.iter_mut() {
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
顺便说一句,在其生命周期std::slice::IterMut<'a, T>
内是不变的。这是因为可变引用通常必须是不变的才能正确。这意味着如果你交换了可变借用和不可变借用的顺序,即使在非 trait 版本中也会出现错误。
fn mutate(&'a mut self) {
let mut vector: Vec<i8> = vec![];
for value in self.iter_mut() {
// This would panic if it compiled, of course
*value = vector[0];
}
for value in self.iter().take(1).cloned() {
vector.push(value);
}
}
所以基于特征的版本不起作用,因为self.iter()
要求借用持续时间太长,并且不能被强制为更短的借用。事实上,就事物的编写方式而言,更短的借用甚至可能没有意义。Self::Iter
可能只为那个特定的生命周期定义。
那么写这个的理想方式是什么?一种方法是将 的实现放在 的mutate
每个实现中AbstractView
。当使用具体类型Iter
andIterMut
时,编译器知道我们可以使用协方差来缩短生命周期。
一个更有原则的解决方案是在它们的生命周期中使用 makeSelf::Iter
和Self::IterMut
generic ,以便可以根据需要缩短借用时间。像这样的通用关联类型还不可能。
在 nightly 编译器上,可以这样做,但正如编译器正确警告的那样,泛型关联类型尚未完成,可能会导致编译器崩溃或错误。
#![feature(generic_associated_types)]
#[derive(Debug)]
struct Foo {
bar: Vec<i8>,
}
trait AbstractView {
type Iterator<'b>: Iterator<Item = &'b i8>;
type IteratorMut<'b>: Iterator<Item = &'b mut i8>;
// Eventually, these lifetimes should be elided
// But it doesn't seem that that's implemented yet
fn iter<'a>(&'a self) -> Self::Iterator<'a>;
fn iter_mut<'a>(&'a mut self) -> Self::IteratorMut<'a>;
fn mutate(&mut self) {
let mut vector: Vec<i8> = vec![];
for value in self.iter().take(1).cloned() {
vector.push(value);
}
for value in self.iter_mut() {
*value = vector[0];
}
}
}
#[derive(Debug)]
struct View<'a> {
foo: &'a mut Foo,
}
impl<'a> AbstractView for View<'a> {
type Iterator<'b> = std::slice::Iter<'b, i8>;
type IteratorMut<'b> = std::slice::IterMut<'b, i8>;
fn iter<'b>(&'b self) -> Self::Iterator<'b> {
self.foo.bar.iter()
}
fn iter_mut<'b>(&'b mut self) -> Self::IteratorMut<'b> {
self.foo.bar.iter_mut()
}
}
fn main() {
let mut foo = Foo { bar: vec![0, 1, 2] };
let mut view = View { foo: &mut foo };
view.mutate();
println!("{:?}", foo);
}
推荐阅读
- visual-studio-code - avr-g++.exe:错误:设备规格/规格-avr2:没有这样的文件或目录
- javascript - querySelector 会导致错误,而 getElementById 不会
- r - k-means 聚类与生存数据
- python - 如何获得另一列等于特定值的列的总和?
- python - Python函数来自csv的多个分组数据
- python-3.x - 使用最近邻缩放绘制或调整绘制的量化图像
- typescript - 在 Visual Studio Code 中调试 TypeScript 渲染器
- oracle - 在页面加载 oracle apex 上设置页面项目值
- java - 如何删除红色的 JFormDesigner 评估文本?
- testing - 使用 Cypress 和 Gatsby 测试该页面不是 404 页面