Rust 学习笔记:Rc<T>

Rust 学习笔记:Rc<T>

在某些情况下,单个值可能有多个所有者。例如,在图数据结构中,多个边可能指向同一个节点,并且该节点在概念上由指向它的所有边拥有。一个节点不应该被清理,除非它没有任何边指向它,因此没有所有者。

Rc<T> 显式地启用多重所有权,这是引用计数的缩写。Rc<T> 类型跟踪对一个值的引用次数,以确定该值是否仍在使用。

使用引用计数指针共享数据

尝试实现下图所示的数据结构,共有 a、b、c 三个列表,其中 b、c 共享 a。

在这里插入图片描述

我们先用 Box<T> 定义的 cons 列表试试:

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
    let b = Cons(3, Box::new(a));
    let c = Cons(4, Box::new(a));
}

程序执行报错。Cons 变量拥有它们保存的数据,所以当我们创建 b 列表时,a 被移动到 b 中,而 b 拥有 a。然后,当我们试图在创建 c 时再次使用 a 时,我们不允许这样做,因为 a 已经被移动了。

在这里插入图片描述

我们可以将 Cons 的定义改为保存引用,但这样就必须指定生命周期形参。通过指定生命周期参数,我们将指定列表中的每个元素至少与整个列表一样长,但并不是每个场景都是如此。所以这不是一个好办法。

正确的做法是改变 List 的定义,使用 Rc<T> 代替 Box<T>。每个 Cons 变量现在都将保存一个值和一个 Rc<T>,该指针指向一个 List。

当我们创建 b 时,我们将克隆 a 所持有的 Rc<List>,而不是获取 a 的所有权,从而将引用的数量从 1 增加到 2,并让 a 和 b 共享数据的所有权。我们还将在创建 c 时再次克隆 a,将引用的数量从 2 增加到 3。每次调用 Rc::clone 时,对 Rc<List> 中的数据的引用计数将增加,并且除非对数据的引用为 0,否则数据不会被清理。

修改之前的代码:

use crate::List::{Cons, Nil};
use std::rc::Rc;

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}

我们需要添加一个 use 语句来将 Rc<T> 带入作用域,因为它不在 prelude 中。当我们创建 b 和 c 时,我们调用 Rc::clone 函数,并将一个 a 的不可变引用作为参数。

我们本可以调用 a.clone(),而不是 Rc::clone(&a),但是 Rust 的惯例是在这种情况下使用 Rc::clone,因为它只增加引用计数,而不是像 clone 一样做深拷贝。

通过使用 Rc::clone 进行引用计数,我们可以直观地区分深拷贝类型的克隆和增加引用计数的克隆。在寻找代码中的性能问题时,我们只需要考虑深度拷贝克隆,并且可以忽略对 Rc::clone 的调用。

直观感受引用计数

修改之前的代码:

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));
    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}

程序输出:

在这里插入图片描述

通过调用 Rc::strong_count 函数,能输出值上的引用计数。

这个函数被命名为 strong_count 而不是 count,因为 Rc<T> 类型也有 weak_count。

我们可以看到 a 中的 Rc<List> 的初始引用计数为 1,然后每次调用 Rc::clone,计数增加 1。当 c 超出作用域时,计数减少 1。

我们必须调用 Rc::clone 来增加引用计数,但我们不需要调用函数来减少引用计数。因为 Rc<T> 实现了 Drop trait,当值超出作用域时自动减少引用计数。

在这个例子中我们看不到的是,当 b 和 a 在 main 函数的末尾超出作用域时,计数为 0,并且 Rc<List> 被完全清除。

通过不可变引用,Rc<T> 允许在程序的多个部分之间共享仅用于读取的数据。假如 Rc<T> 允许有多个可变引用,同一位置的多个可变借用会导致数据竞争和不一致,这不被所有权系统允许。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值