Goal Of Rust - Safety

Goal of Rust - Safety

Rust programs are free from:

  1. Dangling pointers - Live references to data that has become invalid over the course of the program.
  2. Data Races - The inability to determine how a program will behave from run to run because external factors change.
  3. Buffer Overflow - An attempt to access the 12th element of an array with only 6 elements.
  4. Iterator Invalidations - An issue caused by something that is iterated over after being altered midway through.

I have been reading the book Rust in Action, which is a wondeful book and I liked the way the author had described in simple terms using simple programs, the safety of Rust. I liked it so much that I thought the best way to remember these are to write them out in my journal.

To explain the concepts further the following are the code to illustrate the above points:

  1. Dangling Pointer
#[derive(Debug)]
enum Cereal {
    Barley,
    Millet,
    Rice,
    Rye,
    Spelt,
    Wheat,
}

fn main() {
    let mut grains: Vec<Cereal> = vec![];
    grains.push(Cereal::Rye);
    drop(grains);
    println!("{:?}", grains);
}

The listing will not compile and wil complain about an attempt to borrow a moved value.

error[E0382]: borrow of moved value: `grains`
  --> src/main.rs:15:22
   |
12 |     let mut grains: Vec<Cereal> = vec![];
   |         ---------- move occurs because `grains` has type `Vec<Cereal>`, which does not implement the `Copy` trait
13 |     grains.push(Cereal::Rye);
14 |     drop(grains);
   |          ------ value moved here
15 |     println!("{:?}", grains);
   |                      ^^^^^^ value borrowed here after move

For more information about this error, try `rustc --explain E0382`.
error: could not compile `dangling_pointer` due to previous error

  1. Data Race Condition
use std::thread;
fn main() {
    let mut data = 100;

    thread::spawn(|| {
        data = 500;
    });
    thread::spawn(|| {
        data = 1000;
    });
    println!("{}", data);
}

Here the program is not deterministic and it’s impossible to know what value data will hold when main() executes.

The thread spawned on line 5 is attempting to set the data variable to 500, whereas the thread spawned on line 6 is attempting to set it to 1,000. Because the scheduling of threads is determined by the OS rather than the program, it’s impossible to know if the thread defined first will be the one that runs first.

error[E0373]: closure may outlive the current function, but it borrows `data`, which is owned by the current function
 --> src/main.rs:5:19
  |
5 |     thread::spawn(|| {
  |                   ^^ may outlive borrowed value `data`
6 |         data = 500;
  |         ---- `data` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:5:5
  |
5 | /     thread::spawn(|| {
6 | |         data = 500;
7 | |     });
  | |______^
help: to force the closure to take ownership of `data` (and any other referenced variables), use the `move` keyword
  |
5 |     thread::spawn(move || {
  |                   ++++

error[E0499]: cannot borrow `data` as mutable more than once at a time
 --> src/main.rs:8:19
  |
5 |       thread::spawn(|| {
  |       -             -- first mutable borrow occurs here
  |  _____|
  | |
6 | |         data = 500;
  | |         ---- first borrow occurs due to use of `data` in closure
7 | |     });
  | |______- argument requires that `data` is borrowed for `'static`
8 |       thread::spawn(|| {
  |                     ^^ second mutable borrow occurs here
9 |           data = 1000;
  |           ---- second borrow occurs due to use of `data` in closure

error[E0373]: closure may outlive the current function, but it borrows `data`, which is owned by the current function
  --> src/main.rs:8:19
   |
8  |     thread::spawn(|| {
   |                   ^^ may outlive borrowed value `data`
9  |         data = 1000;
   |         ---- `data` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/main.rs:8:5
   |
8  | /     thread::spawn(|| {
9  | |         data = 1000;
10 | |     });
   | |______^
help: to force the closure to take ownership of `data` (and any other referenced variables), use the `move` keyword
   |
8  |     thread::spawn(move || {
   |                   ++++

error[E0502]: cannot borrow `data` as immutable because it is also borrowed as mutable
  --> src/main.rs:11:20
   |
5  |       thread::spawn(|| {
   |       -             -- mutable borrow occurs here
   |  _____|
   | |
6  | |         data = 500;
   | |         ---- first borrow occurs due to use of `data` in closure
7  | |     });
   | |______- argument requires that `data` is borrowed for `'static`
...
11 |       println!("{}", data);
   |                      ^^^^ immutable borrow occurs here

Some errors have detailed explanations: E0373, E0499, E0502.
For more information about an error, try `rustc --explain E0373`.
error: could not compile `data_race` due to 4 previous errors

  1. Buffer Overflow
fn main() {
      let fruit = vec!["Orange", "Banana", "Pineapple"];
  
      let buffer_overflow = fruit[4];      
      assert_eq!(buffer_overflow, "")    
  }

When the program is compiled,this is the error message that is returned:

 Finished dev [unoptimized + debuginfo] target(s) in 0.53s
     Running `target/debug/buffer_overflow`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 4', src/main.rs:4:29
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  1. Iterator Invalidation
fn main() {
    let mut letters = vec!["a", "b", "c"];

    for letter in letters {
        println!("{}", letter);
        letters.push(letter.clone());
    }}

Attempting to modify an iterator while iterating over it is not allowed and the compiler throws the following error message:

error[E0382]: borrow of moved value: `letters`
   --> src/main.rs:6:9
    |
2   |     let mut letters = vec!["a", "b", "c"];
    |         ----------- move occurs because `letters` has type `Vec<&str>`, which does not implement the `Copy` trait
3   | 
4   |     for letter in letters {
    |                   -------
    |                   |
    |                   `letters` moved due to this implicit call to `.into_iter()`
    |                   help: consider borrowing to avoid moving into the for loop: `&letters`
5   |         println!("{}", letter);
6   |         letters.push(letter.clone());
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
    |
note: this function takes ownership of the receiver `self`, which moves `letters`
   --> /home/ankur/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:234:18
    |
234 |     fn into_iter(self) -> Self::IntoIter;
    |                  ^^^^

For more information about this error, try `rustc --explain E0382`.
error: could not compile `iterator_invalidation` due to previous error

References:

  1. Rust in Action