ProgrammingBackend Developer

How do smart pointers work in Rust (Box, Rc, Arc, RefCell)? What are the differences between them, and in which cases should you choose which?

Pass interviews with Hintsage AI assistant

Answer

In Rust, there is no traditional garbage collector, so smart pointers are used to manage ownership of complex structures. The most commonly used are:

  • Box<T> — allocates memory for an object on the heap and transfers ownership of it. It is used when the size of data is not known at compile time or when a movable but unique resource is required.

  • Rc<T> (Reference Counted) — reference counting, allows multiple variables to "share" ownership of immutable data (only in a single-threaded context).

  • Arc<T> (Atomic Reference Counted) — also implements reference counting, but atomic; applicable in multithreaded programs.

  • RefCell<T> — provides "interior mutability" at runtime, allowing the contents to be changed even through an immutable reference, but only in one thread (not thread-safe!).

Example:

use std::rc::Rc; let a = Rc::new(vec![1,2,3]); let b = Rc::clone(&a); // Now both a and b own the same data

Trick Question

Can Rc<T> be used in multithreaded code if all threads only read data? Explain.

Answer: No, it can't! Even though Rc<T> allows only immutable access to data, the Rc<T> container itself is not thread-safe, as its internal reference count is not protected against data races. This is what Arc<T> is for—its internal counter is thread-safe.

Example:

// The following code will not compile! use std::thread; use std::rc::Rc; let five = Rc::new(5); for _ in 0..10 { let five = Rc::clone(&five); thread::spawn(move || { println!("{}", five); }); }

Examples of real errors due to ignorance of the nuances of the topic


Story

They tried to use Rc<T> to share a cache between threads to speed up a web service. In production, they encountered strange crashes and corrupted data. Upon investigation, it was found that Rc is not thread-safe, and the reference count got corrupted. Solution: replace with Arc<T>.

Story

In a desktop application, a large tree of objects was stored in Box<T>, but they did not consider that multiple parts of the UI needed to share ownership of the data. This led to compilation errors. The solution was to use Rc<T> for shared access.

Story

In a business logic module, they used RefCell<T> to organize mutable access to data that was also passed through Arc<T> between threads. But the attempt to combine RefCell<T> and Arc<T> led to races and panic at runtime. For a thread-safe option, they should have used Mutex<T> or RwLock<T> instead of RefCell<T>.