理解 Rust 的借用和解引用

5

我正在阅读Rust文档,但似乎无法理解正在发生的事情。例如,在这里,我看到了以下示例:

// This function takes ownership of a box and destroys it
fn eat_box_i32(boxed_i32: Box<i32>) {
    println!("Destroying box that contains {}", boxed_i32);
}

// This function borrows an i32
fn borrow_i32(borrowed_i32: &i32) {
    println!("This int is: {}", borrowed_i32);
}

fn main() {
    // Create a boxed i32, and a stacked i32
    let boxed_i32 = Box::new(5_i32);
    let stacked_i32 = 6_i32;

    // Borrow the contents of the box. Ownership is not taken,
    // so the contents can be borrowed again.
    borrow_i32(&boxed_i32);
    borrow_i32(&stacked_i32);

    {
        // Take a reference to the data contained inside the box
        let _ref_to_i32: &i32 = &boxed_i32;

        // Error!
        // Can't destroy `boxed_i32` while the inner value is borrowed later in scope.
        eat_box_i32(boxed_i32);
        // FIXME ^ Comment out this line

        // Attempt to borrow `_ref_to_i32` after inner value is destroyed
        borrow_i32(_ref_to_i32);
        // `_ref_to_i32` goes out of scope and is no longer borrowed.
    }

    // `boxed_i32` can now give up ownership to `eat_box` and be destroyed
    eat_box_i32(boxed_i32);
}

我相信的事情:

  1. eat_box_i32 接受一个指向 Box 的指针
  2. 这行代码 let boxed_i32 = Box::new(5_i32); 使得 boxed_i32 包含了一个指针,因为 Box 不是原始类型

我不理解的事情:

  1. 为什么我们需要使用 & 来调用 borrow_i32(&boxed_i32);?难道 boxed_i32 不已经是一个指针了吗?
  2. 在这一行代码中: let _ref_to_i32: &i32 = &boxed_i32; 为什么右边也需要加上 &?难道 boxed_i32 不已经是一个地址了吗?
  3. 为什么 borrow_i32 可以接受指向 Box 和指向 i32 的指针?
2个回答

13

关于“指针”术语的评论

如果你愿意,你可以跳过这部分内容,但考虑到你提出的问题,这可能是一个有用的评论:

在Rust中,&i32&mut i32*const i32*mut i32Box<i32>Rc<i32>Arc<i32>都可以说是“指向i32”类型。但是,在这些指针类型之间进行转换时需要注意,即使它们在内存中的布局相同,Rust也不会轻易地让你进行转换。

有时候谈论指针的总体概念可能很有用,但作为一个经验法则,如果你想弄清楚为什么一段Rust代码能编译通过而另一段却不能,我建议你要牢记你正在使用哪种类型的指针。


您的信仰:

  1. eat_box_i32接受一个指向Box<i32>的指针。

事实并非如此。 eat_box_i32接受一个Box<i32>,而不是指向Box<i32>的指针。恰好在内存中,Box<i32>被存储为指向i32的指针。

  1. 这一行代码let boxed_i32 = Box::new(5_i32);boxed_i32包含一个指针,因为Box不是原始类型。

是的,boxed_i32是一个指针。


你不理解的事情:

  1. 为什么我们需要使用&符号调用borrow_i32(&boxed_i32);?boxed_i32不已经是一个指针了吗?

是的,boxed_i32已经是一个指针了。然而,封装的指针仍然表示所有权。如果你传递boxed_i32而不是&boxed_i32,你仍然会传递一个指针,但Rust将认为该变量已被“消耗”,并且在该函数调用之后你将无法再使用boxed_i32。

  1. 在这一行中:let _ref_to_i32: &i32 = &boxed_i32; 为什么右侧需要&符号?boxed_i32不已经是一个地址了吗?

是的,boxed_i32已经是一个地址,但它是不透明的(就像一个只有一个私有字段的struct)。&boxed_i32的实际类型是&Box<i32>。

虽然这很奇怪,对吧?如果&boxed_i32是&Box<i32>,那么你怎么能将其赋值给类型为&i32的变量呢?

这实际上是一种简写--如果类型T实现了Deref<Target=R> trait,它将自动将类型&T的值转换为类型&R。而Box<T>类型实现了Deref<Target=T>。

有关Deref的更多信息,请参见https://doc.rust-lang.org/std/ops/trait.Deref.html

因此,如果你没有使用该自动转换,那么该行实际上看起来会像这样:

let _ref_to_i32: &i32 = Deref::deref(&boxed_i32);

原因与上面的(2)相同。 borrow_i32接受&i32作为其参数。传递&i32显然是可以的,因为类型完全匹配。如果您尝试传递&Box<i32>,Rust将自动将其转换为&i32,因为Box<i32>实现了Deref<i32>

编辑:感谢 @kmdreko 指出 Deref 允许强制转换,而不是 AsRef


1

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