Rust学习笔记-先来写个链表吧(一)

116 阅读3分钟

学习一门编程语言,主要关注几个方面:

  • 基本语法与数据类型:学习如何实现一些简单的功能,此时一般会关注内存模型
  • IO类库:学习如何进行字节、字符操作,磁盘操作,网络操作等
  • 线程类库:学习如何编写多线程相关的功能

这些功能是一门语言的核心功能,学完这些,就可以去看一些框架了。

所以最开始,先用Rust来写一个链表吧。

啃了大概几十页的《Rust权威指南》之后,依照我多年的java开发经验,我写的链表目前是这样子:

struct Node {
    value: u32,
    next: Node,
}

struct LinkedList {
    head: Node,
    size: u32,
}

这是一个异常简单的链表结构,简单到没用泛型,甚至只包含结构体,连方法都没有实现。但是编译器上来就给了我一闷棍:

error[E0072]: recursive type `Node` has infinite size
 --> src\main.rs:5:1
  |
5 | struct Node {
  | ^^^^^^^^^^^ recursive type has infinite size
6 |     value: u32,
7 |     next: Node,
  |           ---- recursive without indirection
  |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Node` representable
  |
7 |     next: Box<Node>,
  |           ++++    +

这里是说Node类型具有无限的大小,而 Rust必须在编译时知道每一种类型占据的空间大小 ,对于递归的类型而言,无法在编译时被确定具体大小。

同时,编译器也给出了解决方案,即使用Box来存储指向下一个对象的指针,而不是直接存储对象,由于Box是一种智能指针,具有确定的大小,便能够通过编译。

Box使我们可以将数据存储在堆上,并在栈中保留一个指向堆数据的指针。-- 《Rust权威指南》

修改后的代码如下:

struct Node {
    value: u32,
    next: Box<Node>,
}

struct LinkedList {
    head: Box<Node>,
    size: u32,
}

这样修改之后,cargo check能够顺利通过。但在接下来添加初始化方法的过程中出现了问题——由于Rust没有类似于Java语言中的null概念,所以上面的数据结构是无法初始化的,编译器会一直要求你输入下一个Node……

这样的代码在编译器那里是过不去的:

impl LinkedList {
    fn new() -> LinkedList {
        return LinkedList { head: Nil, size: 0 };
    }
}

所以此处还需要用到Option

Option是Rust标准库中的枚举,用来标识一个值无效或缺失。在标准库中的源码如下:

enum Option<T> (
    Some(T),
    None,
)

Some 和 None 都仅仅是 Option 这个枚举的变体,并没有什么特殊的意义。但在编码过程中我们可以将None视为类似于空值之类的东西,从而显式地去处理它。

引入Option后,链表结构体长这样:

struct Node {
    value: u32,
    next: Option<Box<Node>>,
}

struct LinkedList {
    head: Option<Box<Node>>,
    size: u32,
}

impl LinkedList {
    fn new() -> LinkedList {
        return LinkedList {
            head: None,
            size: 0,
        };
    }
}

cargo check通过,至此,链表的基本结构体算是基本确定下来,但真正的麻烦才刚刚开始,因为接下来要学习编写各种方法。