๐Ÿฆ€ Functional Rust
๐ŸŽฌ Fearless Concurrency Threads, Arc>, channels โ€” safe parallelism enforced by the compiler.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข std::thread::spawn creates OS threads โ€” closures must be Send + 'static

โ€ข Arc> provides shared mutable state across threads safely

โ€ข Channels (mpsc) enable message passing โ€” multiple producers, single consumer

โ€ข Send and Sync marker traits enforce thread safety at compile time

โ€ข Data races are impossible โ€” the type system prevents them before your code runs

986: Mutex-Protected State

Difficulty: Beginner Category: Async / Concurrency FP Patterns Concept: Wrapping mutable state in a mutex to prevent data races Key Insight: Rust's `Mutex<T>` owns the data โ€” you can't access `T` without holding the lock; the `MutexGuard` RAII pattern makes forgetting to unlock impossible

Versions

DirectoryDescription
`std/`Standard library version using `std::sync`, `std::thread`
`tokio/`Tokio async runtime version using `tokio::sync`, `tokio::spawn`

Running

# Standard library version
cd std && cargo test

# Tokio version
cd tokio && cargo test
// 986: Mutex-Protected State
// Rust: Mutex<T> owns the data โ€” unlocks automatically via RAII guard

use std::sync::{Arc, Mutex};
use std::thread;

// --- Approach 1: Shared counter with Arc<Mutex<i32>> ---
fn shared_counter() -> i32 {
    let counter = Arc::new(Mutex::new(0i32));

    let handles: Vec<_> = (0..10).map(|_| {
        let counter = Arc::clone(&counter);
        thread::spawn(move || {
            for _ in 0..100 {
                let mut n = counter.lock().unwrap();
                *n += 1;
                // Lock released here when `n` drops
            }
        })
    }).collect();

    for h in handles { h.join().unwrap(); }
    let x = *counter.lock().unwrap(); x
}

// --- Approach 2: Mutex around structured state ---
#[derive(Debug)]
struct BankAccount {
    balance: i64,
    transactions: u32,
}

impl BankAccount {
    fn new() -> Self { BankAccount { balance: 0, transactions: 0 } }

    fn deposit(&mut self, amount: i64) {
        self.balance += amount;
        self.transactions += 1;
    }

    fn withdraw(&mut self, amount: i64) -> bool {
        if self.balance >= amount {
            self.balance -= amount;
            self.transactions += 1;
            true
        } else {
            false
        }
    }
}

fn bank_account_demo() -> (i64, u32) {
    let account = Arc::new(Mutex::new(BankAccount::new()));

    let handles: Vec<_> = (0..5).map(|_| {
        let acct = Arc::clone(&account);
        thread::spawn(move || {
            acct.lock().unwrap().deposit(100);
        })
    }).collect();

    for h in handles { h.join().unwrap(); }

    let mut acct = account.lock().unwrap();
    acct.withdraw(200);
    (acct.balance, acct.transactions)
}

// --- Approach 3: with_mutex helper (bracket / RAII equivalent) ---
fn with_lock<T, R, F: FnOnce(&mut T) -> R>(m: &Mutex<T>, f: F) -> R {
    let mut guard = m.lock().unwrap();
    f(&mut *guard)
    // guard drops here โ€” unlock is automatic
}

fn collect_to_vec() -> Vec<i32> {
    let shared = Arc::new(Mutex::new(Vec::<i32>::new()));

    let handles: Vec<_> = (0..5i32).map(|i| {
        let shared = Arc::clone(&shared);
        thread::spawn(move || {
            with_lock(&shared, |v| v.push(i));
        })
    }).collect();

    for h in handles { h.join().unwrap(); }

    let mut v = shared.lock().unwrap().clone();
    v.sort();
    v
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_shared_counter() {
        assert_eq!(shared_counter(), 1000);
    }

    #[test]
    fn test_bank_account() {
        let (balance, txns) = bank_account_demo();
        assert_eq!(balance, 300);  // 5*100 - 200
        assert_eq!(txns, 6);       // 5 deposits + 1 withdrawal
    }

    #[test]
    fn test_collect_to_vec() {
        let v = collect_to_vec();
        assert_eq!(v, vec![0, 1, 2, 3, 4]);
    }

    #[test]
    fn test_mutex_protects_from_data_race() {
        // If counter were unprotected, this would be UB/wrong
        assert_eq!(shared_counter(), 1000);
    }

    #[test]
    fn test_with_lock_helper() {
        let m = Mutex::new(0i32);
        with_lock(&m, |v| *v += 1);
        with_lock(&m, |v| *v += 1);
        assert_eq!(*m.lock().unwrap(), 2);
    }
}
(* 986: Mutex-Protected State *)
(* OCaml: Mutex.t wraps a mutable reference โ€” shared counter *)

(* --- Approach 1: Simple shared counter --- *)

let counter = ref 0
let m = Mutex.create ()

let increment () =
  Mutex.lock m;
  incr counter;
  Mutex.unlock m

let () =
  let threads = List.init 10 (fun _ ->
    Thread.create (fun () ->
      for _ = 1 to 100 do increment () done
    ) ()
  ) in
  List.iter Thread.join threads;
  assert (!counter = 1000);
  Printf.printf "Approach 1 (counter): %d\n" !counter

(* --- Approach 2: Mutex-protected record (structured state) --- *)

type bank_account = {
  mutable balance: int;
  mutable transactions: int;
}

let account = { balance = 0; transactions = 0 }
let account_m = Mutex.create ()

let deposit amount =
  Mutex.lock account_m;
  account.balance <- account.balance + amount;
  account.transactions <- account.transactions + 1;
  Mutex.unlock account_m

let withdraw amount =
  Mutex.lock account_m;
  if account.balance >= amount then begin
    account.balance <- account.balance - amount;
    account.transactions <- account.transactions + 1;
    Mutex.unlock account_m;
    true
  end else begin
    Mutex.unlock account_m;
    false
  end

let () =
  let threads = List.init 5 (fun _ ->
    Thread.create (fun () -> deposit 100) ()
  ) in
  List.iter Thread.join threads;
  assert (account.balance = 500);
  assert (account.transactions = 5);
  let ok = withdraw 200 in
  assert ok;
  assert (account.balance = 300);
  Printf.printf "Approach 2 (account): balance=%d txns=%d\n"
    account.balance account.transactions

(* --- Approach 3: with_mutex helper (bracket pattern) --- *)

let with_lock m f =
  Mutex.lock m;
  let result = (try f () with e -> Mutex.unlock m; raise e) in
  Mutex.unlock m;
  result

let shared_list = ref []
let list_m = Mutex.create ()

let () =
  let threads = List.init 5 (fun i ->
    Thread.create (fun () ->
      with_lock list_m (fun () ->
        shared_list := i :: !shared_list
      )
    ) ()
  ) in
  List.iter Thread.join threads;
  let sorted = List.sort compare !shared_list in
  assert (List.length sorted = 5);
  Printf.printf "Approach 3 (with_lock): [%s]\n"
    (String.concat "; " (List.map string_of_int sorted))

let () = Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Mutex-Protected State โ€” Comparison

Core Insight

Both languages use mutexes for shared mutable state, but Rust's type system enforces correct use: `Mutex<T>` wraps the data itself, so you physically cannot access it without locking. OCaml's `Mutex.t` is a separate object โ€” the programmer must remember to lock/unlock around every access.

OCaml Approach

  • `Mutex.create ()` creates a mutex independent of the data
  • `Mutex.lock m` / `Mutex.unlock m` must be called manually โ€” easy to forget
  • Exception-unsafe: if `f()` throws, you must catch and unlock (bracket pattern)
  • `with_lock` helper function needed for exception safety
  • Data lives in `ref` cells separate from the mutex

Rust Approach

  • `Mutex::new(data)` wraps data and mutex together โ€” inseparable
  • `mutex.lock().unwrap()` returns a `MutexGuard<T>` โ€” acts like `&mut T`
  • Guard unlocks automatically when dropped (RAII) โ€” exception safe
  • `Arc<Mutex<T>>` for sharing across threads (atomically ref-counted)
  • Compiler prevents accessing data without locking โ€” zero runtime overhead

Comparison Table

ConceptOCamlRust
Create`Mutex.create ()` + `ref data``Mutex::new(data)`
Lock`Mutex.lock m``m.lock().unwrap()`
Unlock`Mutex.unlock m` (manual)Drop the `MutexGuard` (RAII)
Access dataAccess `ref` directlyDereference guard: `*guard`
Share across threads`ref` in closure`Arc::clone(&mutex)`
Exception safetyManual bracket patternAutomatic via RAII
Forget to unlockPossible (deadlock)Impossible โ€” type system prevents

std vs tokio

Aspectstd versiontokio version
RuntimeOS threads via `std::thread`Async tasks on tokio runtime
Synchronization`std::sync::Mutex`, `Condvar``tokio::sync::Mutex`, channels
Channels`std::sync::mpsc` (unbounded)`tokio::sync::mpsc` (bounded, async)
BlockingThread blocks on lock/recvTask yields, runtime switches tasks
OverheadOne OS thread per taskMany tasks per thread (M:N)
Best forCPU-bound, simple concurrencyI/O-bound, high-concurrency servers