Concurrent Programming in Rust: A Detailed Explanation of Core Methods for Data Sharing Between Threads
In modern computing, multithreaded programming is key to enhancing application performance and achieving high concurrency. However, data sharing between threads has always been a significant challenge in concurrent programming, fraught with pitfalls such as data races and deadlocks. The Rust language, with its unique ownership system and strict compile-time checks, provides us with the ability to achieve “fearless concurrency”.

This article will delve into several core methods for implementing multithreaded data sharing in Rust through specific code practices, including <span>static</span> variables, the <span>Box::leak()</span> technique, and atomic reference counting <span>Arc<T></span>. Whether you are a Rust beginner or an experienced developer, this article will help you gain a deeper understanding and application of Rust’s concurrency capabilities.
Rust: Several Methods for Sharing Data in Multithreading
Rust – Data Sharing Between Threads
Rust Multithreading
Methods:
- Using
<span>move</span>to transfer ownership - Using scoped threads to borrow data from a parent thread with a longer lifetime
- Static
<span>Box::leak()</span><span>Arc<T></span>
Practical Implementation
Create a Project and Enter the Project Directory
cargo new share-data
Creating binary (application) `share-data` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
cd share-data
Static
- The value of a static variable is valid for the entire duration of the program
- Has a
<span>'static</span>lifetime - Can only be initialized with constant values
- Represents a memory address and can be referenced
- Will not call drop at the end of the program
- Can be either
<span>mut</span>or non-<span>mut</span>
Using <span>static</span> to Share Data in a Multithreaded Environment
use std::thread;
static DATA: [i32; 5] = [1, 2, 3, 4, 5];
fn main() {
let mut handles = Vec::new();
for _ in 0..6 {
let h = thread::spawn(|| {
println!("Data: {DATA:#?}");
});
handles.push(h);
}
handles.into_iter().for_each(|h| h.join().unwrap());
}
Run
RustJourney/share-data on main [?] is 📦 0.1.0 via 🦀 1.89.0
➜ cargo run
Compiling share-data v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/share-data)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/share-data`
Data: [
1,
2,
3,
4,
5,
]
Data: [
1,
2,
3,
4,
5,
]
Data: [
1,
2,
3,
4,
5,
]
Data: [
1,
2,
3,
4,
5,
]
Data: [
1,
2,
3,
4,
5,
]
Data: [
1,
2,
3,
4,
5,
]
Modifying <span>mut static</span> Variables in a Multithreaded Environment
use std::thread;
static mut COUNTER: u32 = 0;
fn main() {
let mut handles = Vec::new();
for _ in 0..10000 {
let h = thread::spawn(|| unsafe {
COUNTER += 1;
});
handles.push(h);
}
handles.into_iter().for_each(|h| h.join().unwrap());
println!("Counter: {}", unsafe { COUNTER });
}
Run
RustJourney/share-data on main [?] is 📦 0.1.0 via 🦀 1.89.0
➜ cargo run
Compiling share-data v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/share-data)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/share-data`
Counter: 9990
RustJourney/share-data on main [?] is 📦 0.1.0 via 🦀 1.89.0 took 3.1s
➜ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/share-data`
Counter: 9987
RustJourney/share-data on main [?] is 📦 0.1.0 via 🦀 1.89.0
➜ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/share-data`
Counter: 9987
RustJourney/share-data on main [?] is 📦 0.1.0 via 🦀 1.89.0
➜ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/share-data`
Counter: 9990
Why is it not 10000? Because each time the operation <span>COUNTER += 1</span> is not an atomic operation.
This operation roughly consists of three steps:
First step: retrieve the value of <span>COUNTER</span>
Second step: increment <span>COUNTER</span> by 1
Third step: put it back
It is not an atomic operation, at least three steps are involved. Therefore, using mutable static variables shared across threads and modifying them can lead to data races. Hence, it is unsafe, and we should avoid such operations.
The above is an example of immutable Static and mutable Static for sharing data in multithreading.
Box::leak()
Essentially, it is actively leaking memory allocation
- Releases ownership of the Box and promises never to drop it
- From the moment of leak, this Box exists indefinitely
- Since there is no owner, it can be borrowed by any thread as long as the program runs
- Note: Because it causes memory leaks, do not use too many in one program
Using <span>Box::leak()</span> to Share Data Across Multiple Threads
use std::thread;
fn main() {
let data: &'static [i32; 5] = Box::leak(Box::new([1, 2, 3, 4, 5]));
let mut handles = Vec::new();
for _ in 0..5 {
let h = thread::spawn(move || {
println!("Data: {data:?}");
});
handles.push(h);
}
handles.into_iter().for_each(|h| h.join().unwrap());
}
Although the <span>move</span> keyword is used, it does not transfer ownership.
Run
RustJourney/share-data on main [?] is 📦 0.1.0 via 🦀 1.89.0
➜ cargo run
Compiling share-data v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/share-data)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.50s
Running `target/debug/share-data`
Data: [1, 2, 3, 4, 5]
Data: [1, 2, 3, 4, 5]
Data: [1, 2, 3, 4, 5]
Data: [1, 2, 3, 4, 5]
Data: [1, 2, 3, 4, 5]
<span>Arc<T></span>
Atomic Reference Counting
- Similar to
<span>Rc<T></span>, but<span>Arc</span>guarantees that modifications to the reference counter are atomic operations - Used in multithreaded environments
Using <span>Arc<T></span> to Share Ownership Across Multiple Threads
use std::{sync::Arc, thread};
fn main() {
let data = Arc::new([1, 2, 3, 4, 5]);
let mut handles = Vec::new();
for _ in 0..4 {
let local_data = data.clone();
let h = thread::spawn(move || {
println!("Data: {local_data:?}");
});
handles.push(h);
}
handles.into_iter().for_each(|h| h.join().unwrap());
}
Run
RustJourney/share-data on main [?] is 📦 0.1.0 via 🦀 1.89.0
➜ cargo run
Compiling share-data v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/share-data)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/share-data`
Data: [1, 2, 3, 4, 5]
Data: [1, 2, 3, 4, 5]
Data: [1, 2, 3, 4, 5]
Data: [1, 2, 3, 4, 5]
Conclusion
In this article, we explored three main methods for implementing data sharing between threads in Rust:
<span>static</span>variables: Suitable for sharing data that exists for the entire lifecycle of the program and is immutable. Although<span>static mut</span>can achieve mutable data sharing, it bypasses Rust’s borrowing checks, requires<span>unsafe</span>code blocks, and is prone to data races, so it should be used cautiously or in conjunction with other synchronization primitives.<span>Box::leak()</span>: A technique to obtain a reference with a<span>'static</span>lifetime by actively “leaking” memory. It allows dynamically allocated data to be valid throughout the program’s execution, enabling safe borrowing by multiple threads. However, this method essentially causes memory leaks and should not be overused.<span>Arc<T></span>(Atomic Reference Counting): This is the most commonly used and flexible way to share ownership safely across threads in Rust. It manages the reference count through atomic operations, ensuring that data is cleaned up only after all threads have finished using it, making it the preferred solution for multithreaded data sharing.
In summary, Rust provides a diverse and powerful set of tools to tackle the challenges of concurrent programming. Understanding and choosing the appropriate data sharing method based on specific scenarios is a key step in writing efficient and safe concurrent Rust programs. I hope this article’s practice helps you gain a deeper understanding of Rust’s “fearless concurrency”.
References
- https://www.rust-lang.org/en
- https://course.rs/about-book.html
- https://lab.cs.tsinghua.edu.cn/rust/

Give a “like” to let me know you enjoyed it, and a “recommend” to let more “moon seekers” see it.