在Rust中,一个引用如何成为指向指向指针的指针?

10

今天的Rust之谜来自于《Rust编程语言》第一版的第4.9节。借用和引用的示例如下:

fn main() {
    fn sum_vec(v: &Vec<i32>) -> i32 {
        return v.iter().fold(0, |a, &b| a + b);
    }

    fn foo(v1: &Vec<i32>) -> i32 {
        sum_vec(v1);
    }

    let v1 = vec![1, 2, 3];

    let answer = foo(&v1);
    println!("{}", answer);
}

这看起来是合理的。它打印出“6”,这是你期望的,如果sum_vecv 是一个 C++ 引用,它只是一个指向内存位置的名称,与我们在main() 中定义的矢量v1一样。

然后我用以下内容替换了sum_vec的主体:

fn sum_vec(v: &Vec<i32>) -> i32 {
    return (*v).iter().fold(0, |a, &b| a + b);
}

编译并按预期工作。好了,那不是…完全疯狂的。编译器试图让我的生活更轻松,我明白这一点。虽然令人困惑,但我必须记住它作为语言的一个特定习惯,但不是完全疯狂的。 然后我尝试了:

fn sum_vec(v: &Vec<i32>) -> i32 {
    return (**v).iter().fold(0, |a, &b| a + b);
}

它还能工作!到底是怎么回事?

fn sum_vec(v: &Vec<i32>) -> i32 {
    return (***v).iter().fold(0, |a, &b| a + b);
}

type [i32] cannot be dereferenced. 意思是 "无法对 [i32] 类型进行解引用操作"。噢,谢天谢地,这听起来讲得通。但我本来希望早两轮就能搞定!

Rust 中的引用不是 C++ 中的“内存中另一个位置的名称”,那它们到底是什么呢?它们也不是指针,并且关于引用的规则似乎要么很玄学,要么非常特例化。为什么引用、指针和指向指针的指针在这里都可以同样地发挥作用呢?

1个回答

10
规则并非特别的临时性或深奥。检查v的类型以及它的各种引用:
fn sum_vec(v: &Vec<i32>) {
    let () = v;
}

您将获得:
  1. v -> &std::vec::Vec<i32>
  2. *v -> std::vec::Vec<i32>
  3. **v -> [i32]

您已经了解第一个解引用。感谢 Deref 特征,第二个解引用变得简单了。 Vec<T> 解引用为 [T]

在执行方法查找时,有一组直接的规则

  1. 如果类型具有该方法,请使用它并退出查找。
  2. 如果对该类型的引用具有该方法,请使用它并退出查找。
  3. 如果可以对类型进行解引用,请这样做,然后返回到步骤 1。
  4. 否则,查找失败。

Rust 中的引用并不是 C++ 中的“指向内存中另一个位置的名称”,

它们绝对是指向内存中的位置的名称。事实上,它们被编译成与您所知道的 C / C++ 指针相同的代码。


1
当你手动计算方法调用时,它看起来有点像 let v = &vec![42]; <[_]>::iter(&**v);。序列是:Vec 的引用、Vec、Slice、Slice 的引用。 - Josh Lee
谢谢。对于新手的问题感到抱歉;我保证我在 C 和 Python 方面非常擅长。当教材给出一个例子时,我不能不进行实验。 - Elf Sternberg
4
没问题,@ElfSternberg。这并不是一个纯粹的“新手”问题。实际上,我认为只有理解指针/引用的人才会对这个问题感到困惑。主要的棘手之处在于 Rust 提供的人性化设计以及了解其内部工作原理。 - Shepmaster

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