使用 Rust 解反转链表
使用 Rust 实现 Reverse Linked List
反转链表
基础对象:
1#[allow(dead_code)]
2pub struct Node {
3 value: i32,
4 next: Option<Box<Node>>,
5}
6
7#[allow(dead_code)]
8impl Node {
9 fn new(value: i32) -> Node {
10 Node { value, next: None }
11 }
12}实现
1pub fn reverse_linked_list(mut head: Option<Box<Node>>) -> Option<Box<Node>> {
2 let mut prev: Option<Box<Node>> = None;
3 while let Some(mut boxed_node) = head {
4 head = boxed_node.next.take();
5 boxed_node.next = prev;
6 prev = Some(boxed_node);
7 }
8 prev
9}Box 的作用
在 Rust 中,Box<T> 是一个“箱子”类型,它在堆上存储数据。你可以把它看作是一个智能指针,用来封装和管理堆上的内存。
在这个 Node 结构体中,Box<Node> 是一个指向堆上的 Node 实例的指针。Option<Box<Node>> 则意味着这个指针可能有值(即 Some(Box(Node))),也可能没有值(即 None)。
以下是 Box<T> 在这个上下文中的一些主要用途:
- 递归数据结构:在定义递归或者链接的数据结构时,比如链表、树等,
Box<T>是必要的。在你的例子中,Node结构体是一个链表节点,它有一个next字段指向下一个Node。这个next字段需要使用Box<Node>,因为 Rust 需要知道Node结构体的确切大小,而Box<Node>是一个固定大小的指针。 - 转移所有权:当你需要将值的所有权转移到其他函数或变量时,可以使用
Box<T>。Box<T>拥有它所指向的值的所有权,当Box<T>被销毁时,它所指向的值也会被销毁。 - 大对象和内存管理:如果你有一个很大的对象或者需要手动管理内存,
Box<T>是一个很好的工具。因为Box<T>在堆上分配内存,当你不再需要这个值时,Box<T>会自动回收它所使用的内存。
以上就是 Box<T> 的一些主要用途。在你的例子中,Box<T> 主要用于定义递归的链表数据结构。
不使用 Box 可行?
在定义递归数据结构时,如链表或树,Rust 语言要求必须使用 Box 或者其他形式的间接寻址(如 Rc、Arc 等)。这是由 Rust 的所有权和内存安全性规则决定的。
Rust 编译器需要在编译时就知道每种类型的确切大小。对于递归类型,例如链表节点 Node,如果不使用 Box 或其他形式的指针,Node 类型会有一个无限的大小。这是因为每个 Node 都包含另一个 Node,这形成了一个无限的嵌套。这会导致编译时错误,因为 Rust 无法确定类型的确切大小。
Box(以及 Rc、Arc 等)是一个固定大小的指针类型,它在堆上存储数据。这意味着,对于 Box<Node> 类型,无论 Node 的实际大小如何,Box<Node> 类型总是有一个固定的大小。这使得在 Rust 中定义递归类型成为可能。
所以,对于你的例子,如果你要在 Node 中定义一个指向另一个 Node 的字段,你必须使用 Box 或其他形式的指针。否则,你会得到一个编译错误。
和 Rc<RefCell<T>> 的区别
Box 和 Rc<RefCell<T>> 都是 Rust 中的智能指针类型,但是它们的用途和行为不同。
Box<T>提供了在堆上分配一个值的能力,并拥有这个值的所有权。当Box被丢弃(drop)时,它包含的值也会被丢弃。Box只有一个所有者。Rc<T>是一个引用计数类型,可以让一个值有多个所有者。每当你克隆一个Rc指针,引用计数就会增加。当一个Rc指针被丢弃时,引用计数就会减少。只有当引用计数为0时,值才会被丢弃。RefCell<T>提供了内部可变性。在 Rust 中,我们不能同时拥有一个值的可变引用和不可变引用。然而,有时我们可能需要在运行时改变一个值,即使我们拥有的是一个不可变引用。这就是RefCell发挥作用的地方。RefCell允许我们在运行时借用和改变值,但是如果我们违反了借用规则(例如,同时拥有可变引用和不可变引用),RefCell就会导致程序 panic。
当你看到 Option<Rc<RefCell<TreeNode>>> 时,这是因为在一些情况下(例如,在树或图结构中),我们需要一个节点有多个所有者,或者我们需要修改一个被多个地方引用的值。在这种情况下,我们就需要使用 Rc 和 RefCell。
然而,请注意,Rc<RefCell<T>> 在运行时执行借用检查,可能会引发 panic,而且引用计数会增加运行时开销。在可以确定一个值只有一个所有者,并且不需要在运行时修改的情况下,使用 Box<T> 会更简单、更安全、更高效。
单元测试
1#[cfg(test)]
2mod tests {
3 use super::*;
4
5 #[test]
6 fn test_take() {
7 let mut x = Some(2);
8 let y = x.take();
9 assert_eq!(x, None);
10 assert_eq!(y, Some(2));
11
12 let mut x: Option<u32> = None;
13 let y = x.take();
14 assert_eq!(x, None);
15 assert_eq!(y, None);
16 }
17
18 #[test]
19 fn test() {
20 let mut list = Node::new(1);
21 list.next = Some(Box::new(Node::new(2)));
22 list.next.as_mut().unwrap().next = Some(Box::new(Node::new(3)));
23 list.next.as_mut().unwrap().next.as_mut().unwrap().next = Some(Box::new(Node::new(4)));
24 list.next
25 .as_mut()
26 .unwrap()
27 .next
28 .as_mut()
29 .unwrap()
30 .next
31 .as_mut()
32 .unwrap()
33 .next = Some(Box::new(Node::new(5)));
34 let reversed_list = reverse_linked_list(Option::from(Box::from(list)));
35 assert_eq!(reversed_list.as_ref().unwrap().value, 5);
36 assert_eq!(
37 reversed_list.as_ref().unwrap().next.as_ref().unwrap().value,
38 4
39 );
40 assert_eq!(
41 reversed_list
42 .as_ref()
43 .unwrap()
44 .next
45 .as_ref()
46 .unwrap()
47 .next
48 .as_ref()
49 .unwrap()
50 .value,
51 3
52 );
53 assert_eq!(
54 reversed_list
55 .as_ref()
56 .unwrap()
57 .next
58 .as_ref()
59 .unwrap()
60 .next
61 .as_ref()
62 .unwrap()
63 .next
64 .as_ref()
65 .unwrap()
66 .value,
67 2
68 );
69 assert_eq!(
70 reversed_list
71 .as_ref()
72 .unwrap()
73 .next
74 .as_ref()
75 .unwrap()
76 .next
77 .as_ref()
78 .unwrap()
79 .next
80 .as_ref()
81 .unwrap()
82 .next
83 .as_ref()
84 .unwrap()
85 .value,
86 1
87 );
88 }
89}as_mut().unwrap(),.as_ref().unwrap() 的作用
在 Rust 中,.as_mut() 和 .as_ref() 方法通常被用来转换 Option 或 Result 类型。
.as_mut()是Option和Result类型的一个方法,用来将Option<T>或Result<T, E>转换成Option<&mut T>或Result<&mut T, &mut E>。也就是说,它返回一个包含可变引用的新的Option或Result。.unwrap()是Option和Result类型的一个方法,用来获取它们包含的值。如果Option是Some(v),它返回v,如果Option是None,它会 panic(意味着程序会立即停止,并给出一个错误信息)。对于Result,如果它是Ok(v),它返回v,如果它是Err(e),它也会 panic。.as_ref()是Option和Result类型的一个方法,用来将Option<T>或Result<T, E>转换成Option<&T>或Result<&T, &E>。也就是说,它返回一个包含不可变引用的新的Option或Result。
在你给的代码中,as_mut().unwrap() 和 as_ref().unwrap() 都被用来获取 Option<Box<Node>> 中的 Node。这样,你就可以修改或访问 Node 中的 value 和 next。因为 Option<Box<Node>> 中可能没有 Node(也就是它可能是 None),所以需要使用 unwrap() 来获取 Node。但是请注意,如果 Option<Box<Node>> 是 None,unwrap() 会引发 panic。为了避免这种情况,你需要在调用 unwrap() 之前确保 Option 不是 None。
unwrap() 不能调用多次,其实是所有权问题
1head.unwrap();
2head.unwrap();你遇到的这个错误是由于在 Rust 中的所有权规则所引起的。当你对 Option 类型的值调用 unwrap() 方法时,它会获取 Option 的所有权,这意味着原来的 Option 值将不能再使用。
这就是为什么你在 while 循环中多次使用 head.unwrap().next 时会遇到问题。在第一次调用 unwrap() 之后,head 就被移动(move)了,也就是说它的所有权被转移走了,所以你不能再次对 head 调用 unwrap()。
Option::from 和 Some 是一样的?
是的,你完全正确。在 Rust 中,Some(value) 和 Option::from(value) 是等价的。它们都会创建一个包含 value 的 Option 枚举值。
Some(value) 是更常见的使用方式,它直接创建了一个 Option::Some 枚举变量。例如:
1let some_number = Some(5);Option::from(value) 是 From trait 的一部分,这个 trait 用于定义如何将一种类型转换成另一种类型。Option::from 的定义如下:
1impl<T> From<T> for Option<T> {
2 fn from(value: T) -> Option<T> {
3 Some(value)
4 }
5}因此,Option::from(value) 实际上就是返回 Some(value)。例如:
1let some_number = Option::from(5);在大多数情况下,直接使用 Some(value) 会更简单和直观。
take 函数
1let mut x = Some(2);
2let y = x.take();
3assert_eq!(x, None);
4assert_eq!(y, Some(2));这段代码中的 x.take() 方法是 Option 类型特有的方法。take() 方法将 Option 置为 None 并返回原来的值。
当你调用 x.take() 时,它会把 x 里面的值取出并返回,此时 x 的值就变为了 None,因为你已经取走了它的值。所以,在 x.take() 后,x 的值就是 None 了。
而 y 的值是 x.take() 的返回值,即 x 原来的值 Some(2),所以 y 的值就是 Some(2)。
简单地说,x.take() 做了两件事:
- 将
x设为None。 - 返回
x原来的值。
因此,x 在调用 take 之后的值是 None,而 y 的值就是 x 在调用 take 之前的值,即 Some(2)。
这就是为什么在 assert_eq!(x, None) 和 assert_eq!(y, Some(2)) 的断言语句都能通过,因为它们分别测试的是 x 和 y 的值,这些值都符合预期。