我可以简化使用双重引用的元组表达式吗?

Can I simplify tuple expression that uses a double reference?

我有一个表达式可以创建双重引用变量,只需取消引用该变量即可在调用中使用它。我怀疑有更简单的语法和方法来调用我的函数。

我有两种类型A和B,都不是可移动的,但都是可克隆的。我正在调用一个带有 A 和 B 元组的 Vec 的函数,需要对 Vec 进行索引以获取元组,将元组中的值解构为局部变量,并不变地使用一个值,但另一个可变地使用另一个函数调用。

  pub fn my_func(v: &mut Vec<(&A, &mut B)>, x: &mut C) {
    let i = 0_usize;        // Would be a loop index normally.
    let (a, b) = &mut v[i]; // This makes b a double reference.
    
    x.do_something(*b);     // This expects &mut B as parameter.
  }

如何以一致的方式将签名更改为 my_funcv 的索引以及解构为 ab 以简化事情?我可以使用更少的符号、mut 和取消引用吗?请注意,我不需要 a 可变,只需 b.

忽略 &mut C 因为它是必需的,如果你为每个和号计算一分 &,为每个 mut 计算一分,为每个 deref 计算一分 *,那么你得到8分。获得较少“分数”的解决方案是赢家。

我认为没有任何方法可以简化函数的签名。您可以将 Vec<T> 替换为 [T],前提是您不需要从向量中压入或弹出元素,但此更改不会影响您定义的“分数”。

ab都因为match ergonomics而变成了双重引用。

let 左侧的模式与右侧表达式的“形状”不完全相同,因此编译器会为您“修复”它。表达式的类型是&mut (&A, &mut B)。该模式与外部 &mut 不匹配,因此编译器将其推入元组,给出 (&mut &A, &mut &mut B)。现在形状匹配:外部类型是一个二元组,所以 a&mut &Ab&mut &mut B.

我对您的函数进行了一些修改:

pub struct A;
pub struct B;
pub struct C;

impl C {
    fn do_something(&mut self, b: &mut B) {}
}

pub fn my_func_v1(v: &mut Vec<(&A, &mut B)>, x: &mut C) {
    let i = 0_usize;
    let (a, b) = &mut v[i];

    x.do_something(b);
}

pub fn my_func_v2(v: &mut Vec<(&A, &mut B)>, x: &mut C) {
    let i = 0_usize;
    let &mut (a, &mut ref mut b) = &mut v[i];

    x.do_something(b);
}

pub fn my_func_v2a(v: &mut Vec<(&A, &mut B)>, x: &mut C) {
    let i = 0_usize;
    let (a, &mut ref mut b) = v[i];

    x.do_something(b);
}

pub fn my_func_v3(v: &mut Vec<(&A, &mut B)>, x: &mut C) {
    let i = 0_usize;
    let (a, b) = &mut v[i];
    let (a, b) = (*a, &mut **b);
    // Or:
    //let a = *a;
    //let b = &mut **b;

    x.do_something(b);
}

pub fn my_func_v4(v: &mut Vec<(&A, &mut B)>, x: &mut C) {
    let i = 0_usize;
    let e = &mut v[i];
    let (a, b) = (e.0, &mut *e.1);
    // Or:
    //let a = e.0;
    //let b = &mut *e.1;

    x.do_something(b);
}

v1 是相同的,只是我写了 b 而不是 *b。编译器看到类型为 &mut &mut B 的表达式和类型为 &mut B 的参数,并将透明地取消引用外部引用。如果参数类型是泛型(这里是非泛型 &mut B),这可能不起作用。

v2 是避免 ab 双重引用的“直接”方法。如您所见,它不是很漂亮。首先,我在元组前面添加了 &mut ,以便匹配右侧表达式的类型,以防止 match ergonomics 踢进来。接下来,我们不能只写 b 因为编译器将此模式解释为 move/copy,但我们不能移出可变引用并且 &mut T 不是 Copy&mut ref mut b 是再借的模式版本。

v2a 类似于 v2,但去掉了两边的 &mut(感谢 loganfsmyth 的提醒!)。 v[i](&A, &mut B) 类型,所以它不能移动,但它是一个左值,所以如果我们重新引用它不能是 moved/copied 的部分,那么整个事情就不会发生完全被感动了。

请记住,在模式中,&mut 解构 引用,而 ref mut 构造 引用。现在,&mut ref mut 可能看起来像个傻瓜,但事实并非如此。双可变引用 &mut &mut T 实际上有两个不同的生命周期;让我们将它们命名为 'a 表示内部生命周期,'b 表示外部生命周期,给出 &'b mut &'a mut T'b'a 短)。当您取消引用这样的值时(使用 * 运算符或使用 &mut 模式),输出是 &'b mut T,而不是 &'a mut T。如果它是 &'a mut T,那么您最终可能会得到对同一内存位置的多个可变引用。 b 产生 &'a mut T,而 &mut ref mut b 产生 &'b mut T.

v3 使用取消引用运算符而不是 &mut 模式,希望这样更容易理解。不幸的是,我们需要明确地重新借用 (&mut **b); *b 再次被解释为从可变引用中移出。

b&mut &mut B 并且我们将 b*b 传递给 do_something 时,这两个实际上都不是“正确的”。正确的表达是&mut **b。但是,编译器会在 function/method 调用中自动引用和取消引用参数(包括接收者),但不会在其他上下文中(例如局部变量的初始化程序)。

v4 依靠使用 . 运算符的自动取消引用节省了一对 *

一种选择是显式声明 b 绑定是对 v[i] 的直接引用。这可以用

来完成
let (a, ref mut b) = v[i];

x.do_something(b);

为了简化签名,您的能力非常有限,但一个起点是

pub fn my_func(v: &mut [(&A, &mut B)], x: &mut C) {

假设您的函数实际上并未尝试 add/remove 向量中的项目。