Featured image of post Stanford CS110L Notes 3

Stanford CS110L Notes 3

Stanford CS110L Notes lec04

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.

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy