#287 Jul 28, 2026

287. thread::Builder — Name Your Workers So Panics Tell You Who Died

thread '<unnamed>' panicked — great, which of the eight workers was that? thread::spawn has a configurable sibling, and the two minutes it costs pay off the first time something crashes.

Bite 286 computed how many workers to spawn. Spawn them with thread::spawn and every panic message, debugger view, and profiler trace calls them <unnamed>:

1
2
3
thread::spawn(|| {
    // panics as: thread '<unnamed>' panicked
});

std::thread::Builder (stable since 1.0) is the same spawn with configuration in front:

1
2
3
4
5
6
7
8
9
use std::thread;

let h = thread::Builder::new()
    .name("worker-3".into())
    .spawn(|| {
        // panics as: thread 'worker-3' panicked
        thread::current().name().map(str::to_owned)
    })
    .expect("spawn failed");

Now a crash names the culprit, and the name shows up in gdb/lldb thread lists and profilers too. The code inside can read it back via thread::current().name().

There’s a second difference hiding in that .expect: Builder::spawn returns io::Result<JoinHandle>. Plain thread::spawn panics if the OS can’t create the thread — under fd/memory pressure, exactly when you least want it. With the builder you decide: fall back to fewer workers instead of crashing.

One more knob while you’re there — .stack_size(bytes) for that one worker doing deep recursion, instead of raising RUST_MIN_STACK for the whole program:

1
2
3
4
5
let h = thread::Builder::new()
    .name("deep-parser".into())
    .stack_size(8 * 1024 * 1024)
    .spawn(|| { /* recurse away */ })
    .expect("spawn failed");

Loop it with format!("worker-{i}") and the whole pool from bite 286 is debuggable by name.

← Previous 286. available_parallelism — Stop Hardcoding Your Worker Count Next → 288. std::sync::Barrier — Make All Threads Start Phase 2 Together