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

fre*_*inn 0 reference mutable immutability rust

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

fn f1(mut index: &mut usize, ..)
fn f2(mut index: &mut usize, ..)
Run Code Online (Sandbox Code Playgroud)

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

我已经阅读了变量名之前的“mut”和“:”之后的“mut”有什么区别?中的两个答案。,我已经尝试了几种方法,但仍然无法实现我想要的。我想我不太理解这些概念。

这是我目前理解的证据:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

如果我误解了,我真的很感激对我的代码中发生的事情的一个很好的解释。

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

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

Mat*_* M. 6

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

什么是参考?

引用是一种间接。存在于其他地方的事物的地址。

我们打个比方:

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

目前,让我们忘记typesstackheap:我们只有衣柜和抽屉:

  • 一个在一个抽屉中,它可以移动或复制到另一个抽屉,
  • 引用是该值的地址,表示为一对 ID(衣柜 ID、抽屉 ID)

注意:仅提及抽屉 ID 是无意义的,所有壁橱都有一个抽屉 0...


抽屉如何使用?

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

fn add(left, right) { left + right }
Run Code Online (Sandbox Code Playgroud)

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

Function
  call

+-add-+  <-- New closet
|  3  |  <-- Drawer 0: value 3
+-----+
|  4  |  <-- Drawer 1: value 4
+-----+
Run Code Online (Sandbox Code Playgroud)

注意:在下文中,我将把衣柜表示为数组。这个壁橱将是[3, 4]。我还将使用字母对壁橱进行“编号”,以避免混合壁橱和抽屉,因此这个壁橱可能是d,而第一个抽屉d将是0@d(此处包含 3 个抽屉)。

该函数定义为“取出抽屉0中的值,取出抽屉1中的值,将它们添加到抽屉2中,返回抽屉2中的内容”。

让我们来调味吧;并介绍参考资料:

fn modify() {
    let x = 42;
    let y = &x;
    *y = 32;
}
Run Code Online (Sandbox Code Playgroud)

有什么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);
}
Run Code Online (Sandbox Code Playgroud)

那么,这是怎么回事?

  • 打电话main=>分配一个新的衣柜a: []
  • let mut x = 24;=> a: [24],
  • 打电话mutate_usize_again=>分配一个新的衣柜b: []
  • &mut x=> b: [0@a]
  • *n += 1;=> 将 1 添加到 (0@a) 指向的抽屉n,这意味着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);
}
Run Code Online (Sandbox Code Playgroud)

我们需要 3 个壁橱!

  • 打电话main=>分配一个新的衣柜a: []
  • let mut x = 24=> a: [24],
  • 打电话mutate_usize_two_times=>分配一个新的衣柜b: []
  • n: &mut usize = &mut x=> b: [0@a]
  • *n = 8n=> 将 8 存储在:指向的抽屉中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 += 1z=> 将:指向的抽屉中的值加 1 a: [9]b并且c保持不变。

您可以引用参考,但这不是您在这里的本意。我们确实可以通过定义以下内容来扭曲该示例以使其正常工作:

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

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

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

你的第三个例子

您的第三个示例已增强:

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);
}
Run Code Online (Sandbox Code Playgroud)

我们走吧:

  • 打电话main=>分配一个新的衣柜a: []
  • let x = 42;=> a: [42],
  • 呼叫mutate_usize_one_time_mutable_pointer=> 分配一个新的壁橱b: []
  • n: mut usize = x=> b: [42]
  • n = 48n=> 修改:的值b: [48]注意a没有改变
  • 结束mutate_usize_one_time_mutable_pointer,丢弃b, a: [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);
}
Run Code Online (Sandbox Code Playgroud)

我们走吧:

  • 打电话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]然而生命周期禁止引用指向过去的东西,这是被禁止的。

希望它开始有意义。如果没有……那就去跑步,和朋友出去,清醒一下你的头脑,读另一个解释,再次清醒你的头脑,然后再回来。它会一点一点地渗透进去;)