我开始学习Rust,在实验的过程中,我发现所有权应用于不了解的元组和数组的方式有所不同。基本上,以下代码显示了差异:
#![allow(unused_variables)]
struct Inner {
in_a: u8,
in_b: u8
}
struct Outer1 {
a: [Inner; 2]
}
struct Outer2 {
a: (Inner, Inner)
}
fn test_ownership(num: &mut u8, inner: &Inner) {
}
fn main() {
let mut out1 = Outer1 {
a: [Inner {in_a: 1, in_b: 2}, Inner {in_a: 3, in_b: 4}]
};
let mut out2 = Outer2 {
a: (Inner {in_a: 1, in_b: 2}, Inner {in_a: 3, in_b: 4})
};
// This fails to compile
test_ownership(&mut out1.a[0].in_a, &out1.a[1]);
// But this works!
test_ownership(&mut out2.a.0.in_a, &out2.a.1);
}
test_ownership()
的第一次调用未编译,因为预期Rust会发出一个错误,抱怨对out1.a[_]
进行可变且不可变的引用。
error[E0502]: cannot borrow `out1.a[_]` as immutable because it is also borrowed as mutable
--> src/main.rs:27:41
|
27 | test_ownership(&mut out1.a[0].in_a, &out1.a[1]);
| -------------- ------------------- ^^^^^^^^^^ immutable borrow occurs here
| | |
| | mutable borrow occurs here
| mutable borrow later used by call
但是我不明白的是,为什么test_ownership()
的第二次调用没有使借阅检查器变得疯狂?似乎数组被视为一个整体,而不依赖于要访问的索引,但是元组允许对其不同索引进行多个可变引用。
答案 0 :(得分:5)
组像匿名结构一样,访问元组中的元素的行为就像访问结构字段。
结构可以部分借用(也可以部分移动),因此&mut out2.a.0.in_a
仅借用元组的第一个字段。
这不适用于索引。索引运算符可以通过实现Index
和IndexMut
重载,因此&mut out1.a[0].in_a
等效于&mut out1.a.index_mut(0).in_a
。 a.0
仅访问一个字段,而a[0]
则调用一个函数!函数不能部分借用某些东西,因此索引运算符必须借用整个数组。
答案 1 :(得分:2)
这确实是一个有趣的案例。对于第二种情况,它起作用是因为编译器理解借用了结构的不同部分(为此,在nomicon中为there's a section)。对于第一种情况,不幸的是编译器并不聪明(索引通常由运行时计算值执行),因此您需要手动销毁它:
let [mut x, y] = out1.a;
test_ownership(&mut x.in_a, &y);
答案 2 :(得分:1)
元组大小写和索引编制大小写之间的区别是用于执行索引编制的内容。在元组的情况下,我们在有效地将标识符用作结构的语法上使用了语法糖。因为它是一个标识符,所以访问哪个字段必须是静态的。因此,借用检查器可以使用一组简单的规则来确定生命周期,这些规则遵循用于结构字段的相同规则。
在分度的情况下,这在稳定的锈蚀中是固有的动态操作。索引不是标识符,而是表达式。由于稳定的类型系统还没有常量表达式或类型级别值的概念,因此借阅检查器始终将索引视为动态索引,即使在琐碎的情况下(例如您的索引显然是静态的)也是如此。由于它们是动态的,因此无法证明索引不相等,因此借用冲突。