Two Beautiful Rust Programs

This is a short ad of a Rust programming language targeting experienced C++ developers. Being an ad, it will only whet your appetite, consult other resources for fine print.

First Program

fn main() {
  let mut xs = vec![1, 2, 3];
  let x: &i32 = &xs[0];
  xs.push(92);
  println!("{}", *x);
}

This program creates a vector of 32-bit integers (std::vector<int32_t>), takes a reference to the first element, x, pushes one more number onto the vector and then uses x. The program is wrong: extending the vector may invalidate references to element, and *x might dereference a dangling pointer.

The beauty of this program is that it doesnt compile:

error[E0502]: cannot borrow xs as mutable
    because it is also borrowed as immutable
 --> src/main.rs:4:5

     let x: &i32 = &xs[0];
                    -- immutable borrow occurs here
     xs.push(92);
     ^^^^^^^^^^^ mutable borrow occurs here
     println!(x);
              - immutable borrow later used here

Rust compiler tracks the aliasing status of every piece of data and forbids mutations of potentially aliased data. In this example, x and xs alias the first integer in the vectors storage in the heap.

Rust doesnt allow doing stupid things.

Second Program

use std::sync::{Mutex, MutexGuard};

fn main() {
  let mut counter = Mutex::new(0);

  std::thread::scope(|s| {
    for _ in 0..10 {
      s.spawn(|| {
        for _ in 0..10 {
          let mut guard: MutexGuard<i32> =
            counter.lock().unwrap();
          *guard += 1;
        }
      });
    }
  });

  let total: &mut i32 = counter.get_mut().unwrap();
  println!("total = {}", *total)
}

This program creates an integer counter protected by a mutex, spawns 10 threads, increments the counter 10 times from each thread, and prints the total.

The counter variable lives on the stack, and a pointer to these stack data is shared with other threads. The threads have to lock the mutex to do the increments. When printing the total, the counter is read bypassing the mutex, without any synchronization.

The beauty of this program is that it relies on several bits of subtle reasoning for correctness, each of which is checked by compiler:

  1. Child threads dont escape the main function and so can read counter from its stack.
  2. Child threads only access counter through the mutex.
  3. Child threads will have terminated by the time we read total out of counter without mutex.

If any of these constraints are broken, the compiler rejects the code. Theres no need for std::shared_ptr just to defensively make sure that the memory isnt freed under your feet.

Rust allows doing dangerous, clever, and fast things without fear of introducing undefined behavior.

If you like what you see, here are two books I recommend for diving deeper into Rust: