ProgramGuru

Threads, ownership and joining

Threads, ownership and joining

A school tool can compute a subtotal on a worker thread while main coordinates the report. A thread is an independently scheduled execution path. Ownership determines what data the worker may keep, while joining waits for it to finish and retrieves its result.

What you’ll learn

  • Transfer owned inputs to a worker and retrieve its result.
  • Distinguish thread creation from completion at join.

Threads, ownership and joining in action

  1. Move marks to worker

    The worker owns the vector and sums it to 240.

    Move marks to worker
  2. Worker computes 240

    Main waits at join before receiving that integer and printing it.

    Worker computes 240
  3. Join before printing result

    Main cannot reuse the moved marks binding; it can use the returned total.

    Join before printing result
The worker owns the vector and sums it to 240. Main waits at join before receiving that integer and printing it. Main cannot reuse the moved marks binding; it can use the returned total.

Threads, ownership and joining explained

thread::spawn accepts a closure that may outlive the calling scope. A move closure transfers the captured vector into the worker. join returns a Result because the worker may panic. The example prints only after joining, so its displayed order does not depend on which thread the scheduler runs first.

Connect this idea with ownership, moves and cleanup and channels, arc and mutex.

Work through the Rust program

Save the complete source as main.rs in the Rust environment. No standard input is needed.

Rust 2024 · complete program

use std::thread;
fn main() {
  let marks = vec![70, 80, 90];
  let worker = thread::spawn(move || marks.into_iter().sum::<i32>());
  let total = worker.join().expect("worker should complete");
  println!("Total: {total}");
}

Expected output

Total: 240

Follow the execution

The worker owns the vector and sums it to 240. Main waits at join before receiving that integer and printing it. Main cannot reuse the moved marks binding; it can use the returned total.

Try the complete program in the Rust compiler. For tests, Cargo commands and local files, use the specified local environment.

Try a small variation

Your task: Why is the final print after join important?

Rust 2024 · complete program

use std::thread;
fn main() {
  let marks = vec![70, 80, 90];
  let worker = thread::spawn(move || marks.into_iter().sum::<i32>());
  let total = worker.join().expect("worker should complete");
  println!("Total: {total}");
}

Worked answer

It waits until the worker has produced the result. Starting a thread alone does not guarantee it has completed before the next instruction in main.

Mistakes and limits to recognize

Thread creation has overhead and does not guarantee a speedup. Scoped threads can borrow suitable data while the scope ensures they finish. A panic in a worker is reported through join; real applications should choose a recovery policy rather than automatically expecting success.

Check your understanding

Why is the final print after join important?
Choose one answer. You can try again.

Answer and explanation

It waits until the worker has produced the result.

It waits until the worker has produced the result. Starting a thread alone does not guarantee it has completed before the next instruction in main.

Take the idea into your next program

It waits until the worker has produced the result. Starting a thread alone does not guarantee it has completed before the next instruction in main. Thread creation has overhead and does not guarantee a speedup.

Further reading: the official Rust Book on threads, ownership and joining.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Channels, Arc and Mutex →