构建
好了,我们将从构建 list 开始。 对于这个新系统,这非常简单。 new 仍然微不足道,所有字段中都是 None。 另外,由于它有点笨拙,我们也来介绍一下 Node 构造函数:
impl<T> Node<T> {
fn new(elem: T) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Node {
elem: elem,
prev: None,
next: None,
}))
}
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None, tail: None }
}
}
> cargo build
**A BUNCH OF DEAD CODE WARNINGS BUT IT BUILT**
耶!
现在让我们尝试写一下 push_front。因为双向链表要复杂得多,我们需要做更多的工作。在单链表操作可以简化为单线程的情况下,双链表操作相当复杂。
特别是,我们现在需要特别处理一些与空列表有关的边界情况。大多数操作只触及头部或尾部指针。但是,当转换到空列表或从空列表转换时,我们需要同时编辑这两个列表。
要验证我们的方法是否有意义,一个简单的方法是我们保持以下不变量: 每个节点应该有两个指向它的指针。列表中间的每个节点由其前置节点和后续节点指向,而末端的节点则由列表本身指向。
让我们来试一试:
pub fn push_front(&mut self, elem: T) {
// new node needs +2 links, everything else should be +0
let new_head = Node::new(elem);
match self.head.take() {
Some(old_head) => {
// non-empty list, need to connect the old_head
old_head.prev = Some(new_head.clone()); // +1 new_head
new_head.next = Some(old_head); // +1 old_head
self.head = Some(new_head); // +1 new_head, -1 old_head
// total: +2 new_head, +0 old_head -- OK!
}
None => {
// empty list, need to set the tail
self.tail = Some(new_head.clone()); // +1 new_head
self.head = Some(new_head); // +1 new_head
// total: +2 new_head -- OK!
}
}
}
cargo build
error[E0609]: no field `prev` on type `std::rc::Rc<std::cell::RefCell<fourth::Node<T>>>`
--> src/fourth.rs:39:26
|
39 | old_head.prev = Some(new_head.clone()); // +1 new_head
| ^^^^ unknown field
error[E0609]: no field `next` on type `std::rc::Rc<std::cell::RefCell<fourth::Node<T>>>`
--> src/fourth.rs:40:26
|
40 | new_head.next = Some(old_head); // +1 old_head
| ^^^^ unknown field
编译器错误。好的开始。好的开始。
为什么我们不能访问节点上的 prev 和 next 字段?以前我们只有一个 Rc<node> 的时候,它是有效的。似乎 RefCell 正在妨碍我们的工作。
我们应该检查一下文件。
Google 一下 "rust refcell"。
具有动态检查借用规则的可变内存位置
有关更多信息,请参见模块级文档。
点击链接
可共享的可变容器。
Cell 和 RefCell 类型的值可以通过共享引用(即常见的 &T类型)进行更改,而大多数 Rust 类型只能通过唯一(&mut T)引用进行更改。 我们说 Cell 和 RefCell 提供了“内部可变性”,而典型的Rust类型则表现出“继承的可变性”。
Cell 类型有两种:Cell 和 RefCell 。 Cell 提供 get 和 set 方法,这些方法可以通过单个方法调用来更改内部值。 Cell 仅与实现 Copy 的类型兼容。 对于其他类型,必须使用 RefCell 类型,在进行更改之前获取写锁定。
RefCell<T> 使用Rust的生命周期来实现“动态借用”,这是一个可以请求对内部值进行临时、独占、可变访问的过程。RefCell<T> 的借用是在“运行时”进行跟踪的,这与 Rust 的本机参考类型不同,后者在编译时是完全静态跟踪的。 由于RefCell<T> 借用是动态的,因此可以尝试借用已经可变借用的值。 发生这种情况时,将导致线程崩溃。
什么时候选择内部易变性
更常见的继承易变性是一个关键的语言元素,它使 Rust 能够对指针别名进行强有力的推理,静态地防止崩溃错误。因此,继承的易变性是首选,而内部的易变性则是最后的手段。由于 Cell 型使得可变可能被禁止,所以有时候内部可变可能是适当的,甚至必须使用,例如:
将继承的可变性根源引入共享类型
逻辑不可变方法的实现细节
改变 Clone 的实现
将继承的可变性根源引入共享类型
共享的智能指针类型(包括 Rc<T> 和 Arc<T> )提供了可以在多方之间克隆和共享的容器。 由于包含的值可能是多重别名,因此只能借用它们作为共享引用,而不是可变引用。 没有 cell,根本不可能在共享盒内对数据进行修改!
在共享指针类型中加入 RefCell<T> 来重新引入可变性是很常见的:
use std::collections::HashMap; use std::cell::RefCell; use std::rc::Rc; fn main() { let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new())); shared_map.borrow_mut().insert("africa", 92388); shared_map.borrow_mut().insert("kyoto", 11837); shared_map.borrow_mut().insert("piccadilly", 11826); shared_map.borrow_mut().insert("marbles", 38); }
注意,这个例子使用 Rc<T> 而不是 Arc<T> 。RefCell<T> 用于单线程方案。如果在多线程环境中需要共享可变性,请考虑使用 Mutex<T> 。
嘿,Rust的文档仍然很棒。
我们关心的是这句话:
shared_map.borrow_mut().insert("africa", 92388);
特别是 borrow_mut 这个词。看来我们需要明确地借用一个 RefCell。这个 "." 操作不会为我们做的。真奇怪。让我们试试:
pub fn push_front(&mut self, elem: T) {
let new_head = Node::new(elem);
match self.head.take() {
Some(old_head) => {
old_head.borrow_mut().prev = Some(new_head.clone());
new_head.borrow_mut().next = Some(old_head);
self.head = Some(new_head);
}
None => {
self.tail = Some(new_head.clone());
self.head = Some(new_head);
}
}
}
> cargo build
warning: field is never used: `elem`
--> src/fourth.rs:12:5
|
12 | elem: T,
| ^^^^^^^
|
= note: #[warn(dead_code)] on by default
嘿,它构建成功了! 文档再次胜利。
Last updated
Was this helpful?