【问题标题】:Ownership: differences between tuples and arrays in Rust所有权:Rust 中元组和数组的区别
【发布时间】:2020-06-20 21:22:55
【问题描述】:

我开始学习 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() 不会让借用检查器发疯?似乎数组被视为一个独立于被访问索引的整体,但元组允许对其不同索引进行多个可变引用。

【问题讨论】:

    标签: rust


    【解决方案1】:

    元组就像匿名结构,访问元组中的元素就像访问结构字段

    结构可以部分借用(也可以部分移动),所以&mut out2.a.0.in_a 只借用元组的第一个字段。

    这同样不适用于索引。索引运算符可以通过实现IndexIndexMut来重载,所以&mut out1.a[0].in_a等价于&mut out1.a.index_mut(0).in_aa.0 只是访问一个字段,a[0] 调用一个函数!函数不能部分借用东西,所以索引运算符必须借用整个数组。

    【讨论】:

      【解决方案2】:

      这确实是一个有趣的案例。对于第二种情况,它有效,因为编译器知道结构的不同部分是借用的(在 nomicon 中为there's a section)。对于第一种情况,不幸的是编译器并不那么聪明(索引通常由运行时计算的值执行),因此您需要手动销毁它:

      let [mut x, y] = out1.a;
      test_ownership(&mut x.in_a, &y);
      

      【讨论】:

      • 有趣。像你写的那样解构数组需要移动out1.a,所以如果以后我想使用它,那是行不通的,对吧?是否可以使用引用做类似的事情?我怀疑如果不走不安全的路线,这可能是不可能的。
      • @doragasu 您可以使用split_first_mut 或其他拆分函数在数组上的引用上安全地执行此操作。
      • 对于数组的情况,我更喜欢 bind-by-ref:let [ref mut x, ref y] = out1.a;,因为对于大于 2 的数组大小,拆分可能很乏味。
      • @Kitsu:在您使用ref 关键字编写时进行了测试,它运行良好且简单。还得试试split_first_mut
      【解决方案3】:

      元组情况和索引情况之间的区别在于用于执行索引的情况。在元组的情况下,我们使用语法糖而不是结构中的有效标识符。因为它是一个标识符,所以访问哪个字段必须是静态的。因此,借用检查器可以遵循一组简单的规则来确定生命周期,这些规则遵循用于结构字段的相同规则。

      在索引的情况下,这是一种固有的动态操作。索引不是标识符,而是表达式。由于稳定的类型系统还没有常量表达式或类型级别值的概念,因此借用检查器总是将索引视为动态的,即使在像您这样明显是静态的微不足道的情况下也是如此。由于它们是动态的,不能证明索引不相等,所以借用冲突。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-07
        • 1970-01-01
        • 2017-10-20
        • 1970-01-01
        • 2020-02-16
        • 1970-01-01
        • 1970-01-01
        • 2010-10-12
        相关资源
        最近更新 更多