Devlogs

Concurrency and Move Closures in Rust

Today I finally reached the concurrency section after following rustlings + the Rust book. This is a very interesting topic to learn — being able to write concurrent code means you can technically bring down the time required to process something expensive by breaking it down into smaller chunks. The only catch is: you need to respect Rust's rules while doing so, and that's what I learned today.

If you're writing concurrent code, you need to ensure that the lifetime of a variable actually matches the lifetime of the thread.
Here’s a quick example:

use std::{io::{self, Write}, thread};
fn main(){
    io::stdout().flush().unwrap();
    println!("Loop till how long: ");
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    let num_threads = input.trim().parse::<i32>().unwrap();
    thread::spawn(||{
        for i in 1..=num_threads{
            println!("hi from spawned thread {i}");
        };
    });
    println!("{num_threads}");
}

conncurrent · suleman1412/rusty-vibes@72e73b5

So quickly going through the code: we get an input from the user and then try to use that input inside a closure passed to thread::spawn.
However, this throws a compiler error saying that the variable num_threads does not live long enough for the thread — basically, the thread might outlive the variable it's trying to use.

Using move closures with Threads

To fix this, we use a move closure:

thread::spawn(move ||{
	for i in 1..=num_threads{
		println!("hi from spawned thread {i}");
	};
});

What this does is: it makes the closure take ownership of num_threads. That means you won’t be able to use num_threads after the closure (which is fine in this case), but now the thread can safely use it without any lifetime issues.

Also, something worth noting — since the main thread might exit before the spawned thread finishes, the output might not show up at all. If you care about the output, you might want to do this:

let handle = thread::spawn(move || {
	for i in 1..=num_threads {
		println!("hi from spawned thread {i}");
	}
});

handle.join().unwrap(); // Wait for the thread to finish

That’s it for today. Rust makes you work a bit for concurrency, but it gives you guarantees that are totally worth it.