46-Rc
Rc — Easy Rust 中文译本
译文 · 基于 Easy Rust
原文链接: https://dhghomon.github.io/easy_rust/Chapter_45.html
中文参考:kumakichi/easy_rust_chs
Rc
Rc的意思是 “reference counter”(引用计数器)。你知道在Rust中,每个变量只能有一个所有者。这就是为什么这个不能工作的原因:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fn takes_a_string(input: String) {
println!("It is: {}", input)
}
fn also_takes_a_string(input: String) {
println!("It is: {}", input)
}
fn main() {
let user_name = String::from("User MacUserson");
takes_a_string(user_name);
also_takes_a_string(user_name); // ⚠️
}
|
takes_a_string取了user_name之后,你就不能再使用了。这里没有问题:你可以直接给它user_name.clone()。但有时一个变量是一个结构的一部分,也许你不能克隆这个结构;或者String真的很长,你不想克隆它。这些都是Rc的一些原因,它让你拥有多个所有者。Rc就像一个优秀的办公人员。Rc写下谁拥有所有权,以及有多少个。然后一旦所有者的数量下降到0,这个变量就可以消失了。
下面是如何使用Rc。首先想象两个结构:一个叫 City,另一个叫 CityData。City有一个城市的信息,而CityData把所有的城市都放在Vec中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| #[derive(Debug)]
struct City {
name: String,
population: u32,
city_history: String,
}
#[derive(Debug)]
struct CityData {
names: Vec<String>,
histories: Vec<String>,
}
fn main() {
let calgary = City {
name: "Calgary".to_string(),
population: 1_200_000,
// 假装这个字符串非常非常长
city_history: "Calgary began as a fort called Fort Calgary that...".to_string(),
};
let canada_cities = CityData {
names: vec![calgary.name], // 这里用的是 calgary.name,比较短
histories: vec![calgary.city_history], // 但这个 String 很长
};
println!("Calgary's history is: {}", calgary.city_history); // ⚠️
}
|
当然,这是不可能的,因为canada_cities现在拥有数据,而calgary没有。它说:
1
2
3
4
5
6
7
8
9
10
| error[E0382]: borrow of moved value: `calgary.city_history`
--> src\main.rs:27:42
|
24 | histories: vec![calgary.city_history], // 但这个 String 很长
| -------------------- value moved here
...
27 | println!("Calgary's history is: {}", calgary.city_history); // ⚠️
| ^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
|
= note: move occurs because `calgary.city_history` has type `std::string::String`, which does not implement the `Copy` trait
|
我们可以克隆名称:names: vec![calgary.name.clone()],但是我们不想克隆city_history,因为它很长。所以我们可以用一个Rc。
增加use的声明。
1
2
3
| use std::rc::Rc;
fn main() {}
|
然后用Rc把String包围起来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| use std::rc::Rc;
#[derive(Debug)]
struct City {
name: String,
population: u32,
city_history: Rc<String>,
}
#[derive(Debug)]
struct CityData {
names: Vec<String>,
histories: Vec<Rc<String>>,
}
fn main() {}
|
要添加一个新的引用,你必须clone Rc。但是等一下,我们不是想避免使用.clone()吗?不完全是:我们不想克隆整个String。但是一个Rc的克隆只是克隆了指针–它基本上是没有开销的。这就像在一盒书上贴上一个名字贴纸,以表明有两个人拥有它,而不是做一盒全新的书。
你可以用item.clone()或者用Rc::clone(&item)来克隆一个叫item的Rc。所以calgary.city_history有两个所有者。
我们可以用Rc::strong_count(&item)查询拥有者数量。另外我们再增加一个新的所有者。现在我们的代码是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| use std::rc::Rc;
#[derive(Debug)]
struct City {
name: String,
population: u32,
city_history: Rc<String>, // Rc 里的 String
}
#[derive(Debug)]
struct CityData {
names: Vec<String>,
histories: Vec<Rc<String>>, // 装在 Rc 里的 String 的 Vec
}
fn main() {
let calgary = City {
name: "Calgary".to_string(),
population: 1_200_000,
// 假装这个字符串非常非常长
city_history: Rc::new("Calgary began as a fort called Fort Calgary that...".to_string()), // 用 Rc::new() 创建 Rc
};
let canada_cities = CityData {
names: vec![calgary.name],
histories: vec![calgary.city_history.clone()], // .clone() 来增加计数
};
println!("Calgary's history is: {}", calgary.city_history);
println!("{}", Rc::strong_count(&calgary.city_history));
let new_owner = calgary.city_history.clone();
}
|
这就打印出了2。而new_owner现在是Rc<String>。现在如果我们用println!("{}", Rc::strong_count(&calgary.city_history));,我们得到3。
那么,如果有强指针,是否有弱指针呢?是的,有。弱指针是有用的,因为如果两个Rc互相指向对方,它们就不会死。这就是所谓的 “引用循环”。如果第1项对第2项有一个Rc,而第2项对第1项有一个Rc,它们不能到0,在这种情况下,要使用弱引用。那么Rc就会对引用进行计数,但如果只有弱引用,那么它就会死掉。你使用Rc::downgrade(&item)而不是Rc::clone(&item)来创建弱引用。另外,需要用Rc::weak_count(&item)来查看弱引用数。