Метод join() у потока в Rust делает следующее:

Что делает join()?

Блокирует выполнение текущего потока до тех пор, пока целевой поток не завершит свою работу.

В нашем примере:

#![allow(unused)]
fn main() {
thread::spawn(move || println!("From thread: {list:?}"))
    .join()  // ← ОСНОВНАЯ ТОЧКА
    .unwrap();
}

Без join():

#![allow(unused)]
fn main() {
thread::spawn(move || println!("From thread: {list:?}"));
// основной поток продолжает выполнение немедленно
// порожденный поток может не успеть выполниться
// программа может завершиться до выполнения потока
}

С join():

#![allow(unused)]
fn main() {
thread::spawn(move || println!("From thread: {list:?}"))
    .join()  // основной поток ЖДЕТ здесь
    .unwrap();
// основной поток продолжит ТОЛЬКО после завершения порожденного потока
}

Возвращаемое значение:

  • join() возвращает Result<T, Box<dyn Any + Send + 'static>>
  • T - тип значения, возвращаемого из замыкания потока
  • unwrap() извлекает это значение или паникует при ошибке

Пример с возвращаемым значением:

#![allow(unused)]
fn main() {
let handle = thread::spawn(|| {
    println!("Поток выполняется");
    42  // возвращаем значение
});

let result = handle.join().unwrap();
println!("Поток вернул: {}", result); // Напечатает: "Поток вернул: 42"
}

Практический результат в вашем коде:

Гарантирует, что сообщение "From thread: [1, 2, 3]" будет напечатано до того, как основная функция main() завершится и программа закроется.