接着上篇的内容,我们继续
有所忘怀的读者,可以翻出上一篇再看一下。上篇提及要修改一种结构。这里有一种做法:那就是可以把Rc
这时在上篇末尾的代码中添加一个弱指针
// rc_weak.rs
use std::rc::Rc;
use std::rc::Weak;
#[derive(Debug)]
struct LinkedList {
head: Option>>
}
#[derive(Debug)]
struct LinkedListNode {
next: Option>>,
prev: Option>>,
data: T
}
impl LinkedList {
fn new() -> Self {
LinkedList { head: None }
}
fn append(&mut self, data: T) -> Self {
let new_node = Rc::new(LinkedListNode {
data: data,
next: self.head.clone(),
prev: None
});
match self.head.clone() {
Some(node) => {
node.prev = Some(Rc::downgrade(&new_node));
},
None => {
}
}
LinkedList {
head: Some(new_node)
}
}
}
fn main() {
let list_of_nums = LinkedList::new().append(1).append(2).append(3);
println!("nums: {:?}", list_of_nums);
}
append方法增加了一些内容;现在,在返回新创建的head之前,我们需要更新当前head的前一个节点。看起来不错了,但还不够。编译器认为这是无效操作。
当然,可以让append接收一个对self的可变引用,但这意味着只有当所有节点的绑定都可变时,才能向列表追加,从而迫使整个结构成为可变的。而此处真正想要的是一种方法,使整个结构的一小部分是可变的,幸运的是,可以一个名为RefCell的方法。
于是改善步骤如下:
1.载入RefCell
use std::cell::RefCell;
2 将RefCell中封装LinkedListNode
#[derive(Debug)]
struct LinkedListNode {
next: Option>>,
prev: RefCell
3 利用改变append方法来创建一个新的RefCell,并通过RefCell可变借来更新之前的引用:
fn append(&mut self, data: T) -> Self {
let new_node = Rc::new(LinkedListNode {
data: data,
next: self.head.Clone(),
prev: RefCell::new(None)
});
match self.head.Clone() {
Some(node) => {
let mut prev = node.prev.borrow_mut();
*prev = Some(Rc::downgrade(&new_node));
},
None => {
}
}
LinkedList {
head: Some(new_node)
}
}
}
当使用RefCell borrows(借用)时,最好仔细考虑一下我们是否在以一种安全的方式在进行使用,因为在这方面的错误可能会导致运行时崩溃。然而,在这个实现中,很容易看到我们只有一个borrow,关闭块会立即进行丢弃行为。
此时代码可以编译通过。
如前所述,Rust通过在任何给定作用域只允许一个可变引用,用来保护开发者在编译时免受指针混叠问题(pointer aliasing problem )的困扰。然而,在某些情况下,会表现得过于严格,使得明知是安全的代码,由于严格的借用检查,不能通过编译器。对于这些情况,一种解决方案是将借用检查从编译时转移到运行时,这是通过内部可变性实现的。那么在讨论允许内部可变性的类型之前,我们需要理解内部可变性(interior mutability)和继承的可变性 ( inherited mutability)的概念:
内部的可变性允许稍微变通一下借用规则,但这也给开发者带来了负担,因为要确保在运行时不存在两个可变借用。当这些类型将对多个可变引用的检测从编译转移到运行时,如果存在对某个值的两个可变引用,就会出现panic报错。当向用户公开不可变API时,通常使用内部可变性,尽管API内部有可变部分。标准库有两种提供共享可变性的通用智能指针类型:Cell和RefCell。
考虑一下这个代码,要求用两个对bag的可变引用来改变bag:
// without_cell.rs
use std::cell::Cell;
#[derive(Debug)]
struct Bag {
item: Box
}
fn main() {
let mut bag = Cell::new(Bag { item: Box::new(1) });
let hand1 = &mut bag;
let hand2 = &mut bag;
*hand1 = Cell::new(Bag {item: Box::new(2)});
*hand2 = Cell::new(Bag {item: Box::new(2)});
}
当然,这是通不过去的
我们可以通过将bag值封装在一个Cell中来实现这一点,代码更新如下:
// cell.rs
use std::cell::Cell;
#[derive(Debug)]
struct Bag {
item: Box
}
fn main() {
let bag = Cell::new(Bag { item: Box::new(1) });
let hand1 = &bag;
let hand2 = &bag;
hand1.set(Bag { item: Box::new(2)});
hand2.set(Bag { item: Box::new(3)});
}
正如所期望的那样,这个代码可以通过,唯一增加的成本是必须多写一点。但是,额外的运行时成本为零,而且对可变对象的引用仍然是不可变的,还不错吧。
Cell
如果需要为非复制类型提供类似单元格的特征(Cell-like features),那么可以使用RefCell类型。它使用了类似于借用的读/写模式,但将检查移动到运行时,这很方便,但不是零成本。RefCell分发对值的引用,而不是像Cell类型那样按值返回。看下如下代码:
// refcell_basics.rs
use std::cell::RefCell;
#[derive(Debug)]
struct Bag {
item: Box
}
fn main() {
let bag = RefCell::new(Bag { item: Box::new(1) });
let hand1 = &bag;
let hand2 = &bag;
*hand1.borrow_mut() = Bag { item: Box::new(2)};
*hand2.borrow_mut() = Bag { item: Box::new(3)};
let borrowed = hand1.borrow();
println!("{:?}", borrowed);
}
如上所见,可以从hand1和hand2中可变借用bag,尽管它们被声明为不可变变量。要修改bag中的项,我们在hand1和hand2上调用borrow_mut。之后,进行不可改变借用并打印内容。可以通过。
RefCell类型提供了以下两种借用方法:
现在,如果我们尝试在同一个作用域中调用这两个方法,将前面代码的最后一行更改为,结果如下,得到一个运行时的panic。
println!("{:?} {:?}", hand1.borrow(), hand1.borrow_mut());
这是因为具备排他性可变访问的所有权规则相同。但是,对于RefCell,这是在运行时检查的。对于这种情况,必须显式使用bare block来分隔借用,或者使用drop方法来删除引用。
在前一节中,我们简化了使用Cell和RefCell的用法,开发者很可能不需要在实际代码中以这种形式进行应用。这里看看这些类型给带来的一些实际益处。
正如我们前面提到的,绑定的可变性不是细粒度的;一个值要么是不可变的,要么是可变的,如果它是一个结构体或枚举,那么包括所有字段。Cell和RefCell可以把一个不可变的东西变成可变的东西,也允许将一个不可变的结构的部分定义为可变的。
下面的代码用两个整数和一个sum方法扩展了一个结构体,以缓存sum的答案,并返回缓存的值(如果存在的话):
// cell_cache.rs
use std::cell::Cell;
struct Point {
x: u8,
y: u8,
cached_sum: Cell
编译通过,结果如下,请读者对照结果再自习研读一下代码
Rust采用低级系统编程(low-level systems programming)方法来进行内存管理,承诺可以达到类似C语言的性能,有时甚至更好。通过使用所有权、生存期和借用语义,它不需要垃圾收集器就能做到这一点,想来还是挺赞的。至此,已经讲了很多内容,在编程学习中,都是相对较难的内容。凡是熟悉Rust的人喜欢称自己为Rustacean,期待大家能成为资深的Rustacean。有关在运行时所有权管理的内容,真的需要花一些时间,来好好思考一下,这意味着可以开发性价比很高的程序,自然这是很值得的
https://doc.rust-lang.org/book
深入浅出 Rust,2018,范长春
Rust编程之道,2019, 张汉东
The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger
Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger
Beginning Rust ,2018,Carlo Milanesi
Rust Cookbook,2017,Vigneshwer Dhinakaran