Featured image of post Stanford CS110L Notes 2

Stanford CS110L Notes 2

Stanford CS110L Notes lec03

Rust ā€”ā€” A language empowering everyone to build reliable and efficient software.

Lec03: Error Handling

Ownership

Example One:

1
2
3
4
5
6
7
8
9
fn f(s: String) {
	println!("{}", s);
}

fn main() {
	let s = String::from("Hello");
	f(s); // will work
	f(s); // won't work
}

The problem is in the first f(s), main has give s ā€™s ownership to f, and f doesnā€™t give it back. So main lose s ā€™s ownership in the second f(s). By the way, after the first f(s) ends, s will be free.

šŸŒŸExceptionļ¼š

1
2
3
4
5
6
7
8
9
fn om_nom_nom(param: u32) {
    println!("{}", param);
}

fn main() {
    let x = 1;
    om_nom_nom(x); // will work
    om_nom_nom(x); // will work
}

It works fine because u32 implements a ā€œcopy traitā€ that changes what happens when it is assigned to variables or passed as a parameter.

Good news, only primitive types + a handful of others use copy semantics, and you just need to remember those.

Example Two:

1
2
3
4
5
6
fn main() {
    let s = String::from("hello");
    let s1 = &s;
    let s2 = &s;
    println!("{} {}", s, s1);
}

It works fine, because s s1 s2 are all immutable.

Remember, you can have as many read-only pointers to something as you want, as long as no one can change what is being pointed to.

šŸŒŸĀ Counter Example One:

1
2
3
4
5
6
fn main() {
    let s = String::from("hello");
    let s1 = &mut s; // invalid
    let s2 = &s;
    println!("{} {}", s, s1);
}

This fails to compile becauseĀ sĀ is immutable, and on the next line, we try to borrow aĀ mutableĀ reference toĀ s. If this were allowed, we could modify the string usingĀ s1, even though it was supposed to be immutable.

šŸŒŸĀ Counter Example Two:

1
2
3
4
5
6
fn main() {
    let mut s = String::from("hello");
    let s1 = &mut s;
    let s2 = &s;
    println!("{} {} {}", s, s1, s2);
}

This fails again, but for a different reason.

  • We first declareĀ sĀ as mutable. šŸ‘
  • We borrow a mutable reference toĀ s. šŸ‘
  • We try to borrow an immutable reference toĀ s. However, there already exists a mutable reference toĀ s. Rust doesnā€™t allow multiple references to exist when a mutable reference has been borrowed. Otherwise, the mutable reference could be used to change (potentially reallocate) memory when code using the other references least expect it.

šŸŒŸĀ Counter Example Three:

1
2
3
4
5
fn main() {
    let mut s = String::from("hello");
    let s1 = &mut s;
    println!("{} {}", s, s1);
}
  • We first declareĀ sĀ as mutable. šŸ‘
  • We borrow a mutable reference toĀ s. šŸ‘
  • We try to useĀ s. However, the value has been ā€œborrowed outā€ toĀ s1Ā and hasnā€™t been ā€œreturnedā€ yet. As such, we canā€™t useĀ s1.

Example Three:

1
2
3
4
5
6
fn main() {
	let mut s = String::from("hello");
	let s1 = &mut s; // s1 borrows s here
  println!("{}", s1); // return s's ownership after this line
	println!("{}", s)
}

It works fine.

šŸ’­Ā Thinking

ā€œOne thing thatā€™s confusing is why sometimes I need to &var and other times I can just use var: for example, set.contains(&var), but set.insert(var) ā€“ why?"

Answer:

When inserting an item into a set, we want to transfer ownership of that item into the set; that way, the item will exist as long as the set exists. (It would be bad if you added a string to the set, and then someone freed the string while it was still a member of the set.) However, when trying to see if the set contains an item, we want to retain ownership, so we only pass a reference.

Error Handling about null pointer

Introduce Option<T> in Rust

Null pointer is dangerous in C/C++. To solve this problem, we might want some way to indicate to the compiler when a valueĀ mightĀ beĀ NULL, so that the compiler can then ensure code using those values is equipped to handleĀ NULL.

Rust does this with theĀ OptionĀ type. A value of typeĀ Option<T>Ā can either beĀ NoneĀ orĀ Some(value of type T).

Definition of Option<T>:

1
2
3
4
pub enum Option<T> {
    None,
    Some(T),
}

Example:

 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
fn feeling_lucky() -> Option<String> {
    if get_random_num() > 10 {
        Some(String::from("I'm feeling lucky!"))
    } else {
        None
    }
}

// how to use Option<T>?
// mothod 1, use is_none() or is_some() to check
if feeling_lucky().is_none() {
    println!("Not feeling lucky :(");
}

// method 2, use unwrap_or(default), if it's none, use default
let message = feeling_lucky().unwrap_or(String::from("Not lucky :("));

// method 3, a more idiomatical way
match feeling_lucky() {
    Some(message) => {
        println!("Got message: {}", message);
    },
    None => {
        println!("No message returned :-/");
    },
}

Handling errors

Introduce Result<T, E> in Rust

  • C has an absolutely garbage system for handling errors.
  • C++ and many other languages use exceptions to manage error conditions. It works well, but also has many disadvantages
    • failure modes are hard to spot

Rust takes a different, two-pronged approach to error handling

  • unrecoverable error, use panic!
1
2
3
if sad_times() {
  panic!("Sad times!");
}

Panics terminate the program immediately and cannot be caught.

(Side note: itā€™s technically possible to catch and recover from panics, but doing so really defeats the philosophy of error handling in Rust, so itā€™s not advised.)

  • recoverable error

You should return aĀ Result. If you returnĀ Result<T, E>, you can either returnĀ Ok(value of type T)Ā orĀ Err(value of type E)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn poke_toddler() -> Result<&'static str, &'static str> {
    if get_random_num() > 10 {
        Ok("Hahahaha!")
    } else {
        Err("Waaaaahhh!")
    }
}

fn main() {
    match poke_toddler() {
        Ok(message) => println!("Toddler said: {}", message),
        Err(cry) => println!("Toddler cried: {}", cry),
    }
}

Or you could use unwrap instead

1
2
3
4
// Panic if the baby cries:
let ok_message = poke_toddler().unwrap();
// Same thing, but print a more descriptive panic message:
let ok_message = poke_toddler().expect("Toddler cried :(");

If theĀ ResultĀ wasĀ Ok,Ā unwrap()Ā returns the success value; otherwise, it causes a panic.Ā 

expect()Ā does the same thing, but prints the supplied error message when it panics

Written by Jiacheng Hu, at Zhejiang University, Hangzhou, China.

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