Code up a linked list using Rust!
Specifically, we will learn:
- How to write object-oriented rust code and how this code is situated within the paradigm of ownership and borrowing weāve been discussing.
- How to allocate memory on the heap using theĀ
Box
Ā smart pointer type. - How to do error handling in Rust.
- How to represent the idea of a null-pointer usingĀ
Option
. - How to convert a mutable reference to an owned value usingĀ
take()
- (if time permits) How to use traits in Rust.
Code
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| struct Node {
value : u32,
next : Option<Box<Node>>, // next could be nullptr, use Option<>
}
pub struct LinkedList {
head : Option<Box<Node>>,
size : usize,
}
impl Node {
fn new(value : u32, next : Option<Box<Node>>) -> Node{
Node { value: value, next: next }
}
}
impl LinkedList {
pub fn new() -> LinkedList {
LinkedList { head: None, size: 0 }
}
pub fn get_size(&self) -> usize {
self.size
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn push(&mut self, value : u32) {
/* why use self.head.take() ?
* because self.head is Option<Box<Node>>, and LinkedList has its ownership
* now we want to give self.head's ownership to Node::new()
* so we could use .take(), to give self.head's ownership to Node::new()
* btw, take() is for Option<>, is self.head.is_some(), take() return Some()
* leave None to self.head, is self.head.is_none(), take() return None
*/
let new_node = Box::new(Node::new(value, self.head.take()));
self.head = Some(new_node);
self.size += 1;
}
pub fn pop(&mut self) -> Option<u32> {
let node = self.head.take()?; // if self.head is None, return None, else continue
self.head = node.next;
self.size -= 1;
Some(node.value)
}
pub fn display(&self) {
let mut current = &self.head;
let mut result = String::new();
loop {
match current {
Some(node) => {
result = format!("{} {}", result, node.value);
current = &node.next;
},
None => break,
}
}
println!("{}", result);
}
}
fn main() {
let mut list = LinkedList::new();
for i in 1..10 {
list.push(i);
}
list.display();
list.pop();
list.display();
assert!(list.get_size() == 8);
}
|
Explain: When to use unwrap | expect
Example:
First, youāll need to open the file by callingĀ File::open(filename)
.Ā File::open
Ā returns aĀ Result
, since opening the file may fail (e.g. if the filename is invalid).
You could open the file like this:
1
| let file = File::open(filename).unwrap();
|
However, itās generally good style to avoid callingĀ unwrap
Ā orĀ expect
Ā unless the error absolutely should never occur, or unless youāre writing the code inĀ main
Ā or some other high-level function that wonāt be reused in your program. If you useĀ unwrap
Ā orĀ expect
Ā in helper functions, then code might use those helper functions without realizing they could cause panics.
The idiomatic way to deal with this is to write something like the following:
1
| let file = File::open(filename)?;
|
TheĀ ?
operatorĀ is commonly used in Rust to propagate errors without having to repeatedly write a lot of code to check aĀ Result
. That line of code really expands to this:
1
2
3
4
| let file = match File::open(filename) {
Ok(file) => file,
Err(err) => return Err(err),
};
|
If theĀ Result
Ā wasĀ Ok
, then theĀ ?
Ā gives you the returned value, but if the result was anĀ Err
, it causes your function to return thatĀ Err
Ā (propagating the error through the call chain).
Once you have your file open, you can read the lines like so:
1
2
3
4
| for line in io::BufReader::new(file).lines() {
let line_str = line?;
// do something with line_str*
}
|
In this code,Ā line
Ā is aĀ Result<String, io::Error>
. We can safely unwrap itsĀ Ok
Ā value usingĀ ?
, as we did when opening the file. Then, you can add the string to a vector.
Written by Jiacheng Hu, at Zhejiang University, Hangzhou, China.