Rust Lang

Rust Send and Sync Traits Examples

In Rust, we have access to the Send and Sync traits when working with concurrency and multi-threaded applications. The Send and Sync traits are useful as they help us to indicate whether specific types can be safely shared between multiple threads. This is a critical feature in ensuring the thread safety and in safely handling the data ownership across threads.

This tutorial explores the fundamentals of working with Send and Sync traits in the Rust programming language.

NOTE: Before using the Send and Sync traits, it is good to understand that they are unsafe. Hence, they are unsafe to implement.

Rust Send Trait

The Send trait is automatically implemented by all types that can be safely transferred between threads. Hence, if a type implements this trait, sending the values of the type <T> from one thread to another is safe.

Like the Send and the Sync traits, this trait is auto-implemented for most native types in Rust. However, they are not auto-implemented for the types that contain non-sendable types in them.

Rust Sync Trait

Next is the Sync trait. Similarly, this trait is implemented by all the data types that can safely be shared between threads. Hence, if a type implements the Sync trait, we can have multiple references to the data type from different threads without concurrency-related bugs such as data races.

NOTE: Although they may sound similar, the Send trait is only implemented for the types that can be sent to another thread (safely) while the Sync trait is auto-implemented for the types that can be shared between threads.

Examples:

Let us look at some basic examples to understand how to work with the Send and Sync traits within a Rust program.

Example 1: Sending a Value Between Threads

Consider the following example code that demonstrates how to share a value between threads using the Send trait:

use std::thread;

fn main() {
    let num = 10;

    // Spawn a new thread and send value
    let handle = thread::spawn(move || {
        println!("{}", num);
    });

    // Wait for the thread to finish
    handle.join().unwrap();
}

 
In this example, we start by creating a new thread using the “thread::spawn” method and pass it a closure to print the value of the num variable. We then use the “move” keyword to transfer the ownership of the num variable to the new thread.

This is possible since an i32 value in the Rust programming language implements the Send trait by default.

Example 2: Sharing an Immutable Value Between Threads

We can also share an immutable data between threads using any Rust type that implements the Send and Sync traits. An example is an arc type.

You can learn more about the arc type here: https://linuxhint.com/rust-arc.

An example is as shown in the following:

use std::sync::Arc;
use std::thread;

fn main() {
    let num = 10;

    // Spawn multiple threads and share the value
    for _ in 0..5 {
        let num = Arc::clone(&num);

        thread::spawn(move || {
            println!("{}", num);
        });
    }

    // Wait for all threads to finish
    thread::sleep(std::time::Duration::from_secs(1));
}

 
In this example, we demonstrate how we can use a Rust Atomically Reference Counted type to share the ownership of the variable across multiple threads. This allows multiple threads to create immutable references to the variable without causing the data races.

Example 3: Sharing a Mutable Value Between Threads

To share the mutable values between threads in Rust, we can combine an arc type and a Mutex which implement both the Sync and Send traits.

An example is as follows:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let num = Arc::new(Mutex::new(0));

    // Spawn multiple threads and increment the value concurrently
    let threads: Vec<_> = (0..5)
        .map(|_| {
            let num = Arc::clone(&num);
            thread::spawn(move || {
                let mut guard = num.lock().unwrap();
                *guard += 1;
            })
        })
        .collect();

    // Wait all threads to finish.
    for thread in threads {
        thread.join().unwrap();
    }

    // print final value.
    println!("The value is: {}", *num.lock().unwrap());
}

 
This example demonstrates using an Arc and Mutex to share a mutable value across threads.

The Arch type is beneficial in this case as it helps share the ownership between the threads. For interior mutability, we use a Mutex which ensures that only a single thread can access the value at a given table using the lock.

We increment the value in each thread by acquiring the lock, modifying the value, and releasing the lock.

Once done, we print the final value as follows:

The value is: 5

The value is: 5

 
There you have it!

Conclusion

We explored the Send and Sync traits in Rust. We discovered how to use the Send trait to transfer the values between threads while the Sync threads allow us to share the data between threads.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list