Rc<T> is a reference-counted pointer for single-threaded contexts, while Arc<T> (Atomic Reference Counted) is its thread-safe counterpart designed for sharing data across multiple threads. The critical distinction is that Arc<T> uses atomic operations for reference counting, which introduces a slight performance overhead but guarantees memory safety in concurrent environments, whereas Rc<T> is faster but will panic if cloned across thread boundaries.
Use Rc<T> when you need shared ownership within a single thread, such as in complex data structures like graphs or trees where multiple parts of the structure point to the same node. Use Arc<T> whenever the data needs to be accessed or mutated by multiple threads simultaneously. Note that neither type provides interior mutability by default; you must wrap the inner data in RefCell<T> for Rc or Mutex<T>/RwLock<T> for Arc if you need to change the data after creation.
Here is a practical example showing how Rc works in a single-threaded scenario:
use std::rc::Rc;
fn main() {
let data = Rc::new(String::from("Hello, single-threaded world!"));
let clone1 = Rc::clone(&data);
let clone2 = Rc::clone(&data);
println!("Ref count: {}", Rc::strong_count(&data)); // Prints 3
println!("Data: {}", data);
}
In contrast, here is how you use Arc to share data across threads safely:
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(String::from("Hello, multi-threaded world!"));
let mut handles = vec![];
for _ in 0..3 {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("Thread sees: {}", data_clone);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final ref count: {}", Arc::strong_count(&data));
}
The key takeaway is performance versus safety. If your application is strictly single-threaded, Rc is the better choice because it avoids the cost of atomic instructions. However, if there is any possibility of the data being accessed from another thread, you must use Arc. Attempting to move an Rc into a new thread will result in a compile-time error, preventing accidental data races at the type system level.