What would be the proper way to share a mutable reference across multiple objects in the same thread?


#1

My app consists of severals mods. Each of them has a struct implementing a trait, say, ProcessData. There is also an object which runs each data processing step. So it’s like this:

impl App {
	fn run() {
		let stage1 = DataProcessor1::new();
		let stage2 = DataProcessor2::new();

		stage1.do_some_stuff();
		stage2.do_other_stuff();
	}
}

Now, each data processor needs access to a specific IO interface provided by external library. This interface is initialized by App. Each data processor needs a mutable reference to the interface.

struct App {
	// IOInterface doesn't implement Default, therefore Option
	interface: Box<Option<IOInterface>>;
}

fn App {
	fn init_io(&mut self) {
		interface = Box::new(Some(IOInterface { ... }));
	}

	fn run(&mut self) {
		self.init_io();

		let io = self.interface.as_mut().as_mut().unwrap();

		let stage1 = DataProcessor1::new(io);
		let stage2 = DataProcessor2::new(io);

		stage1.do_some_stuff();
		stage2.do_other_stuff();
	}
}

/* *** */

struct DataProcessor1<'a> {
	io: &mut'a IOInterface
}

impl<'a> DataProcessor1<'a> {
	pub fn new(io: &mut IOInterface) {
		DataProcessor1 { io }
	}
}

Now, when I’m trying to compile it, Rust says I can’t borrow mutably twice. I understand why the error is there — I pass mutable reference to two other objects — so what would be the proper way to implement this?

Thank you.


#2

Could you pass the io value as a mut reference parameter? Or does it need to be a field?

If it needs to be a field, you’ll need to use interior mutability with RefCell. You’ll probably also want to wrap that cell in an Rc so your data processors can share ownership of it.


#3

Thank you for the answer. I would prefer to have it as a field since it’s supposed to be used in a lot of places and can be initialized only after application arguments are parsed. I will read about RefCell and Rc.


#4

This seems really relevant to the whole topic as well. It is from the first edition of “The Book,” and it is about choosing how and when different safety checks are done via the type system at compile time or via a runtime check. It is one of the most useful sections in my opinion.


#5

Yeah, that’s a great section. I’m sure we all want as much compile time checking as possible (that’s part of what we love about Rust). That’s partly why I suggested passing the io as a parameter, since that would (presumably) allow keeping compile time checks. RefCell is basically a workaround, a dynamic singlethread reader/writer lock that enforces rustc’s compile time checks at runtime. Besides a slight performance hit, the bigger worry for me is the non-zero chance of failing to maintain the borrow invariants, particularly when code gets a bit complex. The ensuing panic, while helpful in preventing buggy code from continuing, doesn’t do much for stability/reliability/robustness/etc.

Of course, RefCell exists for a reason and there are cases where it’s best/easier/etc to use it. My only suggestions would be to (a) minimize its use and (b) keep control flow/complexity to a minimum so it’s harder to violate (conversely, easier to detect) the borrowing rules dynamically.


#6

Another problem I noticed while trying out Rc + RefCell is that I can’t just make a function returning a mutable reference directly to IO. And because unwrapping Rc<RefCell<Option<IO>>> every time I need to access the object inside produces a lot of boilerplate code, I’m still not sure how to share this object. Perhaps make Option<IO> global and static? Still wouldn’t be able to share a mutable reference. Honestly, I think this situation is quite common, especially for bigger apps. A lot of singleton objects that need access to each other.


#7

Instead of trying to return a mutable IO ref, consider exposing a function that takes a FnMut(&mut IO). You can then do all the boilerplate unwrapping in one place, and callers just give you a closure.

I’d avoid globals/singletons if you can. Making global state available is typically done with the Rc method mentioned here or passing the state around as a parameter (a “context” object, if you will).


#8

Have App pass the &mut IOInterface to each processor on every invocation instead of having them store it. I know this was already suggested but it really will give you more flexibility.


#9

this code will use ARC and MUTEX to share the same reference between two different thread with semaphore principal, iI think you could manage to use this method without threads

use std::thread;
use std::time;
use std::sync;


fn main() {
	// create an arc to hold and share the instance of data, using a mutex to make sure we are writing to resource once.
    let v : sync::Arc<sync::Mutex<Vec<u8>>> = sync::Arc::new(sync::Mutex::new(Vec::new()));
	// clone one instance for thread
    let cln = v.clone();
    thread::spawn(move || {
        let mut i = 0;
        loop {
            thread::sleep(time::Duration::from_millis(1000));

			// lock data in memory
            let mut data = cln.lock().unwrap();
            (*data).push(i);

            for z in (*data).iter() {
                print!("{},", z);
            }
            println!(" from thread 1 {}", "");
            i = i + 1;
        }
    });
    let cln2 = v.clone();
    std::thread::spawn(move || {
        let mut i = 0;
        loop {
            thread::sleep(time::Duration::from_millis(1000));

            let mut data = cln2.lock().unwrap();
            (*data).push(i);

            for z in (*data).iter() {
                print!("{},", z);
            }
            println!(" from thread 2 {}", "");
            i = i + 1;
        }
    });

}

#10

That’s what I did in the end. Since it’s only 1 parameter, it’s not a problem, but I wonder how it’s handled in bigger apps. Something like OSGi framework in Java could be useful.