函数参数如何允许改变变量但不能指向任何其他变量?

How can a function argument allow mutating a variable but not be capable of pointing to any other variable?

我想要一个函数来调用其他相互递归的函数,但我已经有了这样的类型签名:

fn f1(mut index: &mut usize, ..)
fn f2(mut index: &mut usize, ..)

我真的想要一组相互递归的函数,它们只能改变我在 main() 函数中定义的 index 变量,不能 指向任何其他变量。

我已经阅读了 的 2 个答案,并且我尝试了几种方法,但仍然无法实现我想要的。我觉得我不太理解这些概念。

以下是我目前理解的证据:

// with &mut I can change the referred value of n, but then I can't
// pass a mutable reference anywhere
fn mutate_usize_again(n: &mut usize) {
    *n += 1;
    // n += 70; ^ cannot use `+=` on type `&mut usize`
}

fn mutate_usize_two_times(mut n: &mut usize) {
    *n = 8;
    // if I don't write mut n, I can't pass a mutable reference to
    // the mutate_usize_again function
    mutate_usize_again(&mut n);
}

fn mutate_usize_one_time_referred_value(n: &mut usize) {
    *n += 25;
}

// this changes the referred value of n
fn mutate_usize_one_time_mutable_pointer(mut n: usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = 48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

// doesn't work because of lifetimes
// this changes where is pointing a (Copy?) reference of n
// passed value does not change
/*
fn mutate_usize_one_time(mut n: &usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = &48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}
*/

fn main() {
    let mut index = 0;
    mutate_usize_one_time_mutable_pointer(index);
    println!("index after mutate_usize_one_time_mutable_pointer = {}", index);
    mutate_usize_two_times(&mut index);
    println!("index after mutate_usize_two_times = {}", index);
    mutate_usize_one_time_referred_value(&mut index);
    println!("index after mutate_usize_ = {}", index);
}

如果我误解了我的代码,我将不胜感激。

我开始认为我想要的已经完成了:

  1. index 必须引用其更新值 => mut index
  2. index 必须能够更改引用值并将可变引用传递给其他函数。 => &mut usize
  3. 如果它是另一个具有相同类型 (mut index2: &mut usize) 的函数参数,编译器不会让我有 2 个指向相同内存位置的可变引用。

引用是指向其他对象的变量。引用不会折叠,所以在 Rust 中你可以有一个引用的引用。

您的链接问题描述了 x: &mut Tmut x: T 之间的区别。 mut x: &T 表示您可以更改引用指向的位置,但不能更改它指向的值。

传递给函数时将复制非引用值(或移动,如果未实现 Copy)。

一个工作示例:

// with &mut I can change the referred value of n
fn mutate_usize_again(n: &mut usize) {
    *n += 1;
    *n += 70;
}

fn mutate_usize_two_times(n: &mut usize) {
    *n = 8;
    // just pass it along
    mutate_usize_again(n);
}

fn mutate_usize_one_time_referred_value(n: &mut usize) {
    *n += 25;
}

// this changes the **local** value of n
fn mutate_usize_one_time_mutable_pointer(mut n: usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = 48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

fn main() {
    let mut index = 0;
    mutate_usize_one_time_mutable_pointer(index);
    println!("index after mutate_usize_one_time_mutable_pointer = {}", index);
    mutate_usize_two_times(&mut index);
    println!("index after mutate_usize_two_times = {}", index);
    mutate_usize_one_time_referred_value(&mut index);
    println!("index after mutate_usize_one_time_referred_value = {}", index);
}

考虑到你的标题,你对这个原则感到困惑。 简明扼要的答案:

You shall always follow the even rule. You have same amount of slashes and same amount of dereferences. Together they give an even number.

啊!旧 pointer/pointee 问题。别担心,这是一个常见的绊脚石。坚持下去,在某个时候它会发出咔嗒声,然后它就会变得显而易见。

什么是引用?

引用是间接的。存在于别处的事物的地址。

打个比方:

  • 一个函数框架是一个壁橱,有很多抽屉,
  • 每个抽屉可能包含一个值。

暂时,让我们忘记 typesstackheap:我们只有壁橱和抽屉:

  • 一个在一个抽屉里,它可能被移动或复制到另一个抽屉,
  • a reference是这个值的地址,表示为一对ID(壁橱ID,抽屉ID)。

注意:只提抽屉ID是无意义的,所有壁橱都有抽屉0...


如何使用抽屉?

让我们想象一个(无类型)简单的例子,加法函数:

fn add(left, right) { left + right }

现在,当我们调用 add(3, 4) 时会发生什么?

Function
  call

+-add-+  <-- New closet
|  3  |  <-- Drawer 0: value 3
+-----+
|  4  |  <-- Drawer 1: value 4
+-----+

注意:在下文中,我将壁橱表示为数组。这个壁橱是 [3, 4]。我也会 "number" 壁橱使用字母,以避免混淆壁橱和抽屉,所以这个壁橱可以是 d,而 d 的第一个抽屉将是 0@d(其中包含 3 个)。

函数定义为"take the value in drawer 0, take the value in drawer 1, add them together in drawer 2, return content of drawer 2"。

让我们来点有趣的东西;并引入参考文献:

fn modify() {
    let x = 42;
    let y = &x;
    *y = 32;
}

modify 是做什么的?

  • 致电 modify => 给了我们一个新衣橱 a: [],
  • let x = 42; => 我们的衣柜现在是 a: [42],
  • let y = &x; => 我们的衣柜现在是 a: [42, 0@a],

现在是症结所在:*y = 32;。这意味着将 32 放入 y 指向的抽屉中。因此壁橱现在是:a: [32, 0@a].


你的第一个例子

让我们看一下你的第一个例子,在 main 的情况下:

// with &mut I can change the referred value of n, but then I can't
// pass a mutable reference anywhere
fn mutate_usize_again(n: &mut usize) {
    *n += 1;
    // n += 70; ^ cannot use `+=` on type `&mut usize`
}

fn main() {
    let mut x = 24;
    mutate_usize_gain(&mut x);
}

那么,这是怎么回事?

  • 致电 main => 分配了一个新衣橱 a: [],
  • let mut x = 24; => a: [24],
  • 呼叫 mutate_usize_again => 分配了一个新衣橱 b: [],
  • &mut x => b: [0@a],
  • *n += 1; => 将n指向的抽屉加1(0@a),这意味着b不会改变,但是a会改变!现在 a: [25].
  • n += 70 => 将 70 添加到引用中...这没有意义,引用没有算术运算。

你的第二个例子

让我们继续你的第二个例子,扩充:

// Parameter renamed because it's confusing to always use the same letter
fn mutate_usize_again(z: &mut usize) { *z += 1; }

fn mutate_usize_two_times(mut n: &mut usize) {
    *n = 8;
    // if I don't write mut n, I can't pass a mutable reference to
    // the mutate_usize_again function
    mutate_usize_again(&mut n);
}

fn main() {
    let mut x = 24;
    mutate_usize_two_times(&mut x);
}

我们需要 3 个壁橱!

  • 致电 main => 分配了一个新衣橱 a: [],
  • let mut x = 24 => a: [24],
  • 致电 mutate_usize_two_times => 分配了一个新衣橱 b: [],
  • n: &mut usize = &mut x => b: [0@a],
  • *n = 8 => 将 8 存储在 n 指向的抽屉中:a: [8]b 不变,
  • 致电 mutate_usize_again => 分配了一个新衣橱 c: [],
  • z: &mut usize = &mut n => 错误。 &mut n 的类型是 &mut &mut usize 不能赋值给 z,
  • 类型的参数
  • 假设您使用了 z: &mut usize = n => c: [0@a],
  • *z += 1 => z指向的抽屉中的值加1:a: [9]bc不变。

你可以引用一个引用,但这不是你想在这里做的。我们确实可以通过定义来扭曲示例以使其工作:

 fn mutate_usize_again(z: &mut &mut usize) { **z += 1; }
                          ^                   ^~~ dereference TWICE
                          |~~~~~~~~~~~~~~ two levels of reference

在这种情况下,从对mutate_usize_again的调用重新开始:

  • 调用之前,我们有:a: [8]b: [0@a]
  • 致电 mutate_usize_again => 分配了一个新衣橱 c: [],
  • with z: &mut &mut usize = &mut n => c: [0@b] <- 对引用的引用!
  • **z += 1 => 将 z 指向的引用指向的抽屉中的值加 1。 **z += 1**0@b += 1*0@a += 1,它将 a 修改为 a: [9] 并保留 bc 不变。

你的第三个例子

你的第三个例子,增强版:

fn mutate_usize_one_time_mutable_pointer(mut n: usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = 48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

fn main() {
    let x = 42;
    mutate_usize_one_time_mutable_pointer(x);
}

我们走吧:

  • 致电 main => 分配了一个新衣橱 a: [],
  • let x = 42; => a: [42],
  • 呼叫 mutate_usize_one_time_mutable_pointer => 分配了一个新衣橱 b: []
  • n: mut usize = x => b: [42],
  • n = 48 =>修改n的值:b: [48],注意a不变
  • mutate_usize_one_time_mutable_pointer 结束,丢弃 ba: [42]

这里没有涉及指针,函数名是误导。


你最后的例子

你的第四个例子,扩充:

fn mutate_usize_one_time(mut n: &usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = &48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

fn main() {
    let x = 42;
    mutate_usize_one_time(&x);
}

我们走吧:

  • 致电 main => 分配了一个新衣橱 a: [],
  • let x = 42; => a: [42],
  • 致电 mutate_usize_one_time => 分配了一个新衣橱 b: [],
  • n: &size = &x => b: [0@a],
  • 48 => b: [0@a, 48]
  • &48 => b: [0@a, 48, 1@b]
  • n = &48 => 会导致 b: [1@b, 48] 但是生命周期禁止引用指向 过去 的东西,这是被禁止的。

希望它开始有意义。如果没有……那就去 运行,和朋友出去,清醒一下,读另一个解释,再清醒一下,然后再回来。它会一点一点渗入 ;)