【问题标题】:How can a function argument allow mutating a variable but not be capable of pointing to any other variable?函数参数如何允许改变变量但不能指向任何其他变量?
【发布时间】:2017-03-11 11:07:24
【问题描述】:

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

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

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

我已经阅读了来自What's the difference in `mut` before a variable name and after the `:`? 的 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 个指向同一内存位置的可变引用。

【问题讨论】:

  • 你想做什么?修改引用指向的值还是改变引用本身?
  • @TatsuyukiIshi 编辑了关于我的目标的更完整信息
  • @freinn 我猜你还是很困惑。集中注意力,然后试着回忆你的想法并深入研究它们。在你理解一个代码之前,你应该能够一眼就猜到它的作用。我们不能总是帮助你,但我们可以猜测。从您的写作中,我们可以看到您的想法和想法。
  • @VitaliPom question == question => 不知道。 answer == answer => 知识就在那里。

标签: reference rust immutability mutable


【解决方案1】:

啊!旧的指针/指针问题。别担心,这是一个常见的绊脚石。坚持下去,在某些时候它会点击,然后它就会看起来很明显。

什么是参考?

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

我们打个比方:

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

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

  • 一个在一个抽屉里,它可以移动或复制到另一个抽屉里,
  • 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]。我还将使用字母“编号”壁橱,以避免混合壁橱和抽屉,所以这个壁橱可以是dd 的第一个抽屉将是0@d(这里包含 3 个)。 em>

函数定义为“取抽屉0中的值,取抽屉1中的值,在抽屉2中相加,返回抽屉2的内容”。

让我们为事情增添趣味;并介绍参考文献:

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

modify 是做什么的?

  • 致电modify => 为我们提供了一个新衣橱a: []
  • let x = 42; => 我们的衣橱现在是a: [42]
  • let y = &amp;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: []
  • &amp;mut x => b: [0@a],
  • *n += 1; => 将n (0@a) 指向的抽屉加1,这意味着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: &amp;mut usize = &amp;mut x => b: [0@a],
  • *n = 8 => 将 8 存储在 n 指向的抽屉中:a: [8]b 不变,
  • 致电mutate_usize_again => 分配了一个新衣橱c: []
  • 带有z: &amp;mut usize = &amp;mut n => 错误。 &amp;mut n 的类型是 &amp;mut &amp;mut usize,它不能分配给 z 类型的参数,
  • 假设您使用了z: &amp;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: &amp;mut &amp;mut usize = &amp;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: &amp;size = &amp;x => b: [0@a],
  • 48 => b: [0@a, 48]
  • &amp;48 => b: [0@a, 48, 1@b]
  • n = &amp;48 => 会导致 b: [1@b, 48] 但是生命周期禁止引用指向它们过去的东西,这是被禁止的。

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

【讨论】:

    【解决方案2】:

    引用是指向其他东西的变量。引用不会折叠,因此在 Rust 中,您可以拥有对引用的引用。

    您的链接问题描述了x: &amp;mut Tmut x: T 之间的区别。 mut x: &amp;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);
    }
    

    【讨论】:

    • 不错的答案,但我认为我的前 2 个代码 cmets 是错误的,并且仍在您的答案中。
    • 已删除。谢谢。
    【解决方案3】:

    考虑到你的标题,你对这个原则感到困惑。 简短的回答:

    您应始终遵守均等规则。您有相同数量的斜杠和相同数量的取消引用。他们一起给出一个偶数。

    【讨论】:

    • 耶耶耶很高兴为您提供帮助^^
    • 斜杠是/(反斜杠是\)。你到底指的是什么符号?
    • @Shepmaster 反斜杠
    • 这让我更加困惑。此代码中没有反斜杠 anywhere。我真的不知道你想说什么。
    • 请理解,这些答案也是为了让其他偶然发现相同或类似问题的人能够理解。事实上,这个答案质量很差,对更广泛的 Rust 编程社区几乎没有任何意义。事实上,到目前为止,甚至没有任何迹象表明 OP 理解它。
    猜你喜欢
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多