In this lesson, you will learn why Arc is the bridge that allows shared data to travel safely across thread boundaries.
In Rust, the Send trait is a marker trait that tells the compiler a type is safe to be moved to another thread. Most standard types are Send, but when we wrap data in a smart pointer like Arc<T>, the compiler needs to be certain that the inner data T is also safe to move.
The Rule of Arc and Send
An Arc<T> implements Send if and only if its inner type T implements both Send and Sync.
Send: The data can be moved to another thread.Sync: The data can be safely referenced (shared) by multiple threads simultaneously.
Because Arc<T> allows multiple threads to hold a pointer to the same memory location, the inner data is effectively being shared. If T was not Sync, it could lead to data races if one thread modified the memory while another read it. Therefore, Arc enforces that the shared resource must be thread-safe for both sending and concurrent access.
A Practical Example
Consider the following snippet:
use std::sync::Arc;
use std::thread;
fn main() {
// A String is Send and Sync, so Arc<String> is Send.
let data = Arc::new(String::from("Hello, threads!"));
let handle = thread::spawn(move || {
// Arc allows us to move this handle into a new thread.
println!("{}", data);
});
handle.join().unwrap();
}
If you were to store a non-thread-safe type inside an Arc—such as a Rc<T>—the compiler would stop you. Since Rc uses non-atomic reference counting, it is not Sync. If you try to send Arc<Rc<i32>> to a thread, the compiler will complain that Rc does not implement Send, and therefore the entire Arc cannot be sent.
By enforcing these bounds, Rust prevents data races at compile time, ensuring that your shared, reference-counted data doesn't cause undefined behavior when accessed from different CPU cores.
Key Takeaway: An Arc<T> is only Send if its inner type T is both Send and Sync, ensuring that shared ownership does not violate thread-safety guarantees.