1

Is there any way to share data between threads (with Arc) but only allow a single thread to be able to mutate that data?

Something like that would be possible in C but I can't see how to do this in Rust.

Arc<Mutex<T>> allows all threads to mutate, while Arc<T> allows none.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Are you looking for something out of the box? Seems like you could make a wrapper type around `Arc>` that only exposed read operations, and then only send that wrapper to the reading threads, that way they never have direct access to the `Arc>` and thus no way to get write access. – loganfsmyth Mar 06 '19 at 01:59
  • 1
    Anything you can do in C, you can do in Rust the same way -- with raw pointers and unsafe code. I think you should clarify whether you're looking for a safe solution or an `unsafe`-using wrapper. I don't think a solution using only safe code exists (but I could be wrong). – trent Mar 06 '19 at 03:09
  • Also, how should access between the single writing thread and the multiple reading threads be synchronized? A mutex, or is it synchronized in some other way? – trent Mar 06 '19 at 03:11
  • I'm imagining a situation where one thread has a mutable ref to a value and another has an immutable ref. I guess this is not possible with Arc/Mutex but then I might need static lifetime and how do I make sure concurrent read/writes don't happen. – Nikos Kostoulas Mar 06 '19 at 10:14
  • Mutable references are unique, so it's not possible to have a `&mut T` in one thread and a `&T` in another thread; you have to use something like a `Mutex` or an `UnsafeCell`. Bear in mind, reading and mutating a value at the same time is *also* illegal in C, so whatever C code you are imagining is incorrect if it doesn't feature some form of synchronization. – trent Mar 06 '19 at 12:08
  • Yes my mistake. A Mutex though gives the ability for both threads to mutate at will. – Nikos Kostoulas Mar 06 '19 at 13:29
  • **How** would you do this in C? – Shepmaster Mar 10 '19 at 19:35

2 Answers2

1

You can use the type system to wrap an Arc<Mutex<T>> in a way that disallows mutation except by one privileged owner. Here's an example:

use std::sync::Arc;
use std::sync::Mutex;

pub struct Writer<T>(Arc<Mutex<T>>);

impl<T> Writer<T> {
    pub fn new(value: T) -> Self {
        Writer(Arc::new(Mutex::new(value)))
    }

    pub fn reader(&self) -> Reader<T> {
        Reader(Arc::clone(&self.0))
    }

    pub fn set(&self, value: T) {
        *self.0.lock().unwrap() = value;
    }

    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.0.lock().unwrap().clone()
    }
}

pub struct Reader<T>(Arc<Mutex<T>>);

// derive(Clone) uses incorrect bounds, so we must implement Clone manually
// (see https://stackoverflow.com/q/39415052/3650362)
impl<T> Clone for Reader<T> {
    fn clone(&self) -> Self {
        Reader(Arc::clone(&self.0))
    }
}

impl<T> Reader<T> {
    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.0.lock().unwrap().clone()
    }
}

If you put this code in a module, Rust's privacy controls will prove that no user can duplicate a Writer or turn a Reader into a Writer except through the use of unsafe. Therefore you can clone and send Readers to as many threads as you like, but send the Writer only to the particular thread that should have write access.

There are many possible variations on this design; for instance, you could use RwLock instead of Mutex to let multiple readers access the value simultaneously while it's not being written to.

Playground (based on Akiner Alkan's example)

Something like that would be possible in say C

Note that just as in Rust, if you want to do this safely in C, you need some kind of synchronization (a mutex or similar). Rust insists that you be explicit about how to avoid data races. C is different in that it will just assume you know what you're doing and then punish you savagely for writing races. In Rust the idiomatic approach is to use the safe abstractions provided by the standard library. However, if you have some other means of synchronization and can prove that Mutex is unnecessary overhead, you can always just write things in the C way -- raw pointers are essentially the same in both Rust (within an unsafe block) and C.

trent
  • 25,033
  • 7
  • 51
  • 90
0

You can create a wrapper around Arc<Mutex<T>> and set the values via setter method with the key which is provided by the creator of the arc.

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

#[derive(Clone)]
pub struct CustomArc<T> {
    mutable_arc: Arc<Mutex<T>>,
    mutator_key: String,
}

#[derive(Clone)]
struct MyStruct {
    inner_val: i32,
}

impl MyStruct {
    fn set_val(&mut self, val: i32) {
        self.inner_val = val;
    }

    fn get_val(&mut self) -> i32 {
        self.inner_val.clone()
    }
}

impl CustomArc<MyStruct> {
    fn new(val: MyStruct, mutator_key: String) -> CustomArc<MyStruct> {
        CustomArc {
            mutable_arc: Arc::new(Mutex::new(val)),
            mutator_key,
        }
    }

    fn set_inner_val(&mut self, value: i32, mutator_key: String) -> Result<(), SetError> {
        if mutator_key == self.mutator_key {
            self.mutable_arc.lock().unwrap().set_val(value);
            return Ok(());
        }

        Err(SetError::CouldNotSet)
    }

    fn get_inner_val(&self) -> i32 {
        self.mutable_arc.lock().unwrap().get_val()
    }
}

enum SetError {
    CouldNotSet,
}

fn main() {
    let my_struct = MyStruct { inner_val: 3 };

    let custom_arc = CustomArc::new(my_struct, "OwnerKey".to_string());
    let mut custom_arc1 = custom_arc.clone();
    let mut custom_arc2 = custom_arc.clone();
    let mut custom_arc3 = custom_arc.clone();

    thread::spawn(move || {
        println!(
            "Thread1 -> Current Value: {:?}",
            custom_arc1.get_inner_val()
        );
        if let Err(_err) = custom_arc1.set_inner_val(4, "AnotherKey".to_string()) {
            println!("Could not write in thread1");
        }
        println!("Thread1 -> Value: {:?}", custom_arc1.get_inner_val());
    });

    thread::sleep_ms(500);

    thread::spawn(move || {
        println!(
            "Thread2 -> Current Value: {:?}",
            custom_arc2.get_inner_val()
        );
        if let Err(_err) = custom_arc2.set_inner_val(5, "OwnerKey".to_string()) {
            println!("Could not write in thread2");
        }
        println!("Thread2 -> Value: {:?}", custom_arc2.get_inner_val());
    });

    thread::sleep_ms(500);

    thread::spawn(move || {
        println!(
            "Thread3 -> Current Value: {:?}",
            custom_arc3.get_inner_val()
        );
        if let Err(_err) = custom_arc3.set_inner_val(6, "SomeKey".to_string()) {
            println!("Could not write in thread3");
        }
        println!("Thread3 -> Value: {:?}", custom_arc3.get_inner_val());
    });

    thread::sleep_ms(500);
}

Playground

Since your CustomArc is public and the mutable_arc field is private, you should access them via setter and getters from outside of the crate. The owner(possible another threads) of the mutator_key has right to mutate the inner data.

Akiner Alkan
  • 6,145
  • 3
  • 32
  • 68
  • Interesting! What stops someone from accessing the inner mutable_arc directly though? – Nikos Kostoulas Mar 06 '19 at 09:49
  • Ah my bad it's private. So outside the crate should be fine but inside the crate we'll still have access. – Nikos Kostoulas Mar 06 '19 at 09:50
  • @NikosKostoulas yes but you should put such kind of wrapper utils outside already for the correct modularity. – Akiner Alkan Mar 06 '19 at 10:32
  • A separate module under the same crate should be fine I guess! – Nikos Kostoulas Mar 06 '19 at 10:48
  • @NikosKostoulas did not tried to seperate it in my side but I think it should work. If the answer is solved your issue please, mark it as accepted so it will be easier to find solution for later looking users. – Akiner Alkan Mar 06 '19 at 11:02
  • 2
    I see what you are going for, but secret tokens are **not** the way to enforce an API. Use the type system! For instance, you could have two types: `WriterArc`, which does not implement `Clone` but exposes both `get` and `set` methods, and `ReaderArc`, which implements `Clone` but only exposes `get`. Presto, compile time guarantee. No futzing about with getters and setters for secret keys, ew. – trent Mar 06 '19 at 12:05
  • @trentcl, Could you please provide this as an another answer. I am not feeling that kind of confident . So I can look and learn from your answer too ^-^ – Akiner Alkan Mar 07 '19 at 06:18