ProgrammingSystem Programmer

How does work with threads (std::thread), data transmission between threads, and what safe object transfer mechanisms (move) are available in Rust?

Pass interviews with Hintsage AI assistant

Answer.

Historically, multithreading has been accompanied by crashes, races, and leaks, especially with uncontrolled memory sharing. Rust implements the concept of thread safety at the type level — an object can only be transferred to a thread if it implements the necessary traits (Send, Sync). Threads are created via std::thread::spawn, and communication between them is conducted through channels or shared memory with controlled mutation (Mutex, Arc).

Problem: Managing synchronization manually is complex and dangerous. Transferring arbitrary objects between threads without explicit ownership transfer leads to races and crashes.

Solution: Only explicitly movable (move) objects or shared through Arc, Mutex, as well as built-in message channels (std::sync::mpsc, crossbeam). This minimizes errors associated with synchronous and asynchronous data exchange: ownership is always unambiguous.

Code example:

use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(String::from("Hello from thread!")).unwrap(); }); let received = rx.recv().unwrap(); println!("Received: {}", received); }

Key features:

  • Data transmission between threads only through safe abstractions (channels or Arc/Mutex)
  • Requirement for Send/Sync for any objects that are moved between threads
  • Implicit prevention of race conditions through the type system

Trick questions.

Can you continue using an object in the main thread after transferring it via move?

No, once the object is moved (e.g., into a closure in thread::spawn), it cannot be used in the parent thread; the compiler will not allow the code to compile.

Can mutable references (&mut T) be passed between threads?

No, a mutable reference &mut T can exist only in one instance, and the Send trait is not implemented for it by default. To work with mutable data, a wrapper through Mutex/Arc is used.

Why can't Rc<T> be used to share ownership between threads?

Rc<T> does not implement Sync and Send as its internal counter is not thread-safe. For thread-safe sharing, Arc<T> (atomic reference counter) is used.

// Comparison of Rc and Arc use std::sync::Arc; let x = Arc::new(5); // can be cloned and shared among threads

Typical errors and anti-patterns

  • Attempting to pass Rc<T> or objects without Send/Sync between threads
  • Using mutable references outside of a protected wrapper
  • Leaving closed channels unhandled

Real-life example

Negative case

A developer decided to share a string between threads using Rc<String> and puts Rc inside thread::spawn. The code compiles only if force-casted via unsafe, after which the application can crash or work with corrupted data.

Pros:

  • Simplicity of code

Cons:

  • Guaranteed race conditions, crashes

Positive case

Arc<String> + Mutex<String> is used for safe access, or message is sent through a channel without shared ownership.

Pros:

  • Data safety, complete absence of races
  • Transparent scaling to threads

Cons:

  • There are overheads for atomic operations or locks