But what is Arc in Rust and how to use it with rust traits
Using Arc
with traits in Rust is a bit more involved, but I'll do my best to explain it to you step by step.
Understanding Arc
: What is it ?
Arc
stands for "Atomic Reference Counted" and is a type in Rust's standard library (std::sync::Arc
).
It allows you to share ownership of data between multiple threads, ensuring that the data is deallocated only when the last reference to it is dropped.
This is useful when you need to pass data between threads or share it across different parts of your code safely.
Understanding Traits
In Rust, traits define behavior that types can implement.
Think of them as a set of functions that a type must provide to be considered as implementing that trait.
Traits enable you to write generic code that works with any type that implements the required behavior.
Now, let’s see how we can combine Arc
with traits.
Defining the Trait
First, you need to define the trait that describes the behavior you want your types to implement.
For example, let’s create a simple trait called Printable
that requires a print
function.
trait Printable {
fn print(&self);
}
Implementing the Trait for a Struct
Next, let’s create a simple struct and implement the Printable
trait for it.
struct MyData {
data: String,
}
impl Printable for MyData {
fn print(&self) {
println!("MyData: {}", self.data);
}
}
Using Arc
with the Trait
Now, we want to use Arc
to share the MyData
instance across different parts of our code.
use std::sync::Arc;
fn main() {
// Create an instance of MyData
let my_data = MyData {
data: "Hello, Arc!".to_string(),
};
// Create an Arc that holds a reference to the MyData instance
let arc_my_data: Arc<dyn Printable> = Arc::new(my_data);
// Clone the Arc so that we have another reference to the same data
let cloned_arc_my_data = Arc::clone(&arc_my_data);
// Now we can use both Arc references to the same data
arc_my_data.print();
cloned_arc_my_data.print();
}
In this example, we create an Arc
that holds a reference to the MyData
instance.
We use the Arc::clone
function to create additional references to the same data. The print
function is called on both Arc
references, and it works as expected.
Understanding Arc<dyn Trait>
You might have noticed that we used Arc<dyn Printable>
instead of Arc<MyData>
.
This is because Arc
needs to know the size of the type it's holding at compile time, and trait objects like dyn Trait
have a dynamic size that's not known at compile time.
To use Arc
with traits, we need to use the dyn
keyword to indicate that it's a trait object.
Using Arc
with traits allows us to store different types that implement the same trait within the same Arc
, enabling more flexibility and composability in our code.
That’s the basic explanation of using Arc
with traits in Rust. It provides you with a way to share trait objects across threads while ensuring proper memory management.
But What about Data mutation
You cannot directly mutate the data held by an Arc
between different threads.
The whole purpose of Arc
is to provide shared ownership of immutable data across multiple threads in a safe manner.
It enforces the rule that the data inside an
Arc
cannot be mutated once it's shared.
When you have an Arc
, it allows multiple threads to have read-only access to the data simultaneously, but it does not provide a way for multiple threads to mutate the data concurrently.
If you need to mutate the data, you should use interior mutability patterns like Mutex
or RwLock
in combination with Arc
.
Using Mutex
If you want to mutate the data in a thread-safe manner, you can wrap the data inside a Mutex
.
The Mutex
enforces that only one thread can acquire the lock to the data at a time, ensuring exclusive access during the mutation.
use std::sync::{Arc, Mutex};
use std::thread;
struct MyData {
counter: Mutex<u32>,
}
impl MyData {
fn increment_counter(&self) {
let mut counter = self.counter.lock().unwrap();
*counter += 1;
}
}
fn main() {
let my_data = Arc::new(MyData {
counter: Mutex::new(0),
});
let threads: Vec<_> = (0..4)
.map(|_| {
let my_data_clone = Arc::clone(&my_data);
thread::spawn(move || {
my_data_clone.increment_counter();
})
})
.collect();
for t in threads {
t.join().unwrap();
}
let counter_value = *my_data.counter.lock().unwrap();
println!("Final Counter Value: {}", counter_value);
}
Using RwLock
If you need to allow multiple threads to read the data simultaneously but still need exclusive access for mutation, you can use RwLock
(Read-Write Lock).
The important thing to remember is that when you use Mutex
or RwLock
with Arc
, you need to wrap the data in these types first before placing it inside the Arc
.
This ensures that thread-safe access and mutation are guaranteed.
If you need to mutate the data held by an
Arc
from different threads, you should use interior mutability patterns likeMutex
orRwLock
in conjunction withArc
to achieve thread safety.
Subscribe to my Youtube channel
Subscribe to my youtube channel if you are on the lookout for more such awesome content in video format.
Claps Please!
If you found this article helpful I would appreciate some claps 👏👏👏👏, it motivates me to write more such useful articles in the future.
Follow for regular awesome content and insights.
Subscribe to my Newsletter
If you like my content, then consider subscribing to my free newsletter, to get exclusive, educational, technical, interesting and career related content directly delivered to your inbox
Important Links
Thanks for reading the post, be sure to follow the links below for even more awesome content in the future.
Twitter: https://twitter.com/dsysd_dev
Youtube: https://www.youtube.com/@dsysd-dev
Github: https://github.com/dsysd-dev
Medium: https://medium.com/@dsysd-dev
Email: dsysd.mail@gmail.com
Telegram 📚: https://t.me/dsysd_dev_channel
Linkedin: https://www.linkedin.com/in/dsysd-dev/
Newsletter: https://dsysd.beehiiv.com/subscribe
Gumroad: https://dsysd.gumroad.com/
Dev.to: https://dev.to/dsysd_dev/