为什么我不需要显式地借用可变变量?

11

我刚刚编写了一个小的Rust程序,可以计算斐波那契数列并记忆计算结果。虽然它可以工作,但是我对其中的递归调用感到有些困惑,特别是在理解为什么时。(它可能也不符合惯用方法)

以下是程序:

use std::collections::HashMap;

fn main() {
    let n = 42; // hardcoded for simplicity
    let mut cache = HashMap::new();
    let answer = fib(n, &mut cache);
    println!("fib of {} is {}", n, answer);
}

fn fib(n: i32, cache: &mut HashMap<i32,i32>) -> i32 {
    if cache.contains_key(&n) {
        return cache[&n];
    } else {
        if n < 1 { panic!("must be >= 1") }

        let answer = if n == 1 {
            0
        } else if n == 2 {
            1
        } else {
            fib(n - 1, cache) + fib(n - 2, cache)
        };
        cache.insert(n, answer);
        answer
    }
}

我对情况的理解是这样的:

  • main中,let mut cache的意思是“我想能够改变这个哈希表(或重新分配该变量)”。
  • main调用fib时,它传递了&mut cache,表示“我借给你这个对象,你可以改变它”。
  • fib的签名中,cache: &mut Hashmap的意思是“我期望被借用一个可变的HashMap - 带有改变权限的借用”。

(如果我理解错误,请纠正我。)

但是,当fib递归调用fib(n-1, cache)时,我不需要使用fib(n-1, &mut cache),如果我这样做会出现错误:“无法将不可变本地变量cache作为可变类型借用。” 嗯?它不是可变的借用吗?

如果我尝试使用fib(n - 1, &cache),我会得到稍微不同的错误:

error: mismatched types:
expected `&mut std::collections::hash::map::HashMap<i32, i32>`,
   found `&&mut std::collections::hash::map::HashMap<i32, i32>`

看起来好像是在说“我期望的是可变引用,但得到的是可变引用的引用”。

我知道 fib 在递归调用中是借用的,因为如果放弃所有权,就不能在之后调用 cache.insert。而且我知道这不是递归调用的特殊情况,因为如果我定义一个与 fib 几乎相同的函数 fib2,它们可以相互递归调用,也能正常工作。

为什么我不需要显式地借用一个借用的可变变量


你可以传递所有权,并让函数返回一个包含答案和缓存的元组:PlayPen - oli_obk
2个回答

14

您提到的三点几乎完全正确。当编译器不允许您传递&mut cache时,这是因为该值实际上已经被借用了。 cache的类型是&mut HashMap<i32,i32>,因此传递&mut cache会导致类型为&mut &mut HashMap<i32,i32>的值。只传递cache即可得到期望的类型。

特定的错误信息“cannot borrow immutable local variable cache as mutable”是由于变量cache本身不可变,即使它指向的内存(HashMap)是可变的。这是因为参数声明cache: &mut HashMap<i32, i32>没有声明一个mut变量。这类似于letlet mut之间的可变性区别。Rust支持可变参数,在这种情况下将像这样写:mut cache: &mut HashMap<i32, i32>


哦,我不知道在一个不可变的变量中可以有一个可变值。你确定吗?let mut cache = 会使两者都可变 - 是否有一种方法只声明结构可变?此外,如果我在 fib 的顶部添加 cache = &mut HashMap::new();,我会得到一个生命周期错误,但不会得到“您无法重新分配此变量”的错误。 - Nathan Long
另外,有趣的是,“这是可变借用”是通过类型检查的方式进行验证的。感谢您帮助我理解。 :) - Nathan Long
@NathanLong 你确实可以在不可变变量中拥有可变引用。这是因为 Rust 只需要可变引用是唯一的:其他代码块可能无法读取或修改该值。这是编译器借用检查器的一部分,以确保所有 &mut 都保证是唯一的(对于 & 不需要相同)。由于变量始终是唯一的(因为你拥有它们),所以一个不可变的 &mut 引用仍然是唯一的,因此可以进行修改。你可能还想看看这个。 :) - Snorre
1
啊,对了,当然。main 可以有一个哈希表, 打算改变它,但是将其不可变地借给 print - “我只是给你读取的权限,而不是修改它”。 - Nathan Long

0
在你的函数中,cache是外部HashMap的一个引用。如果你仍然写& mut cache你正在尝试改变一个引用,而不是原始的HashMap

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接