1

I have a bit of a difficulty formulating my question concisely enough for a title. Here is the mockup of what I am trying to ask.

I have a Thing struct, a ThingMaker and a ThingProcessor. A ThingMaker must be able to create multiple Things that are then passed to a ThingProcessor to be used.

The catch is that a ThingProcessor needs to have mutable access to the ThingMaker that made the Thing so that the ThingMaker can update its internal state using feedback from the ThingProcessor. While in code provided there is only one ThingMaker, in my real project there are multiple, so the Things need to include information on what object produced them.

The naive approach of storing the mutable reference in the Thing does not work due to the fact that you cannot have more than one mutable reference to an object at a time, and a ThingMaker makes more than one object requiring it.

I also found this question that seems very close to mine, but I cannot seem to adapt it to my case. I could change the definition of Thing so it keeps a reference back to the ThingMaker through a RefCell, but then how is ThingMaker supposed to make Things?

Dizzar
  • 35
  • 3
  • `Thing` can own a refcounted smart-pointer back to its maker, e.g. `Rc>`. See [playground](https://play.rust-lang.org/?version=stable&mode=stable&edition=2021&gist=1305da664c7351791d2a4f916c329e62). – eggyal Jul 05 '22 at 14:33
  • 1
    Another option is to have `ThingMaker` use interior mutability via `RefCell` (or other tool), so that you only need an immutable reference, `&ThingMaker`, in order to mutate it. – kmdreko Jul 05 '22 at 14:41
  • Right, so. @ChayimFriedman I just opened the playground from a google search and started writing code. It seems it defaults to 2018. The structures I described should not be self-referrential, that is, `Thing`s point to their makers, but not the other way around, as @kmdreko said. – Dizzar Jul 05 '22 at 14:49
  • As it stands, I had thought of both proposed approaches. Making the type use interior mutability would be clunky (imo at least. dont like using cells and refcells), so I hope to avoid that. Using a smart pointer may be a good solution, I just had a little bit of trouble figuring out how to construct the `Thing`s, thaks @eggyal for providing an example. – Dizzar Jul 05 '22 at 14:57
  • Another question: Apart from telling `ThingMaker` to update its internal state, does `Thing` need to access any other information about `ThingMaker` or can that `ThingMaker` be thought of us a "sink"? – cadolphs Jul 05 '22 at 14:59
  • 2
    @Dizzar you'll need a `Cell`/`RefCell`/`Mutex` or something with interior mutability to allow for shared mutability, whether its outside the type, `RefCell`, or inside, `ThingMaker { inner: RefCell }`, is up to you. The only other option that wouldn't is if the `Thing`s only had an *identifier* for what `ThingMaker` made it, and have the `ThingProcessor` do the lookup-by-id itself using some other structure. – kmdreko Jul 05 '22 at 15:37
  • Sounds like you're in a single threaded scenario, so to echo other suggestions you need interior mutability, just `RefCell` should do the trick – jsstuball Jul 05 '22 at 15:43

1 Answers1

0

This can be achieved in two ways. Using smart-pointers or introducing internal mutability to the ThingMaker.

Smart-pointers

Using this approach we wrap our reference to ThingMaker in Rc<RefCell> so that we can have multiple mutable references to one object. There is a limitation to this method (correct me if I'm wrong) - you cannot easily make new Things from inside the ThingMakers methods. That is because we cannot easily obtain the Rc reference to an object from inside said object. Note that neither cells nor Rc are thread safe, so if you have a multi-threaded environment use Arc instead of Rc and Mutex instead of cells. Here is an example using Rc<RefCell> smart-pointer. (thanks @eggyal)

In this expample, we rewrote Thing to store a smart-pointer instead of a direct reference:

struct Thing {
    pub parent: Rc<RefCell<ThingMaker>>,
}

we then have to wrap our maker in a Rc<RefCell> smart-pointer, like this:

let maker = Rc::new(RefCell::new(ThingMaker { data: 16 }));

Which we then give to creation function to make Things:

ThingMaker::make_thing(&maker);

Later, we can access and mutate ThingMaker using a Things reference like so:

let mut parent = thing.parent.borrow_mut();
parent.data += 1;

Rc smart-pointer allows shared ownership of ThingMaker between Things, and the RefCell allows us to reintroduce mutability, seeing as Rc is an immutable pointer.

Interior mutability

This method makes it so that we do not need a mutable reference to mutate our object, only an immutable one. As we can have as many immutable referenes as we want, we can make as many Things as we want. To achieve this we can use Cells and RefCells (refer to documentation to see which one will be better in you case). Additionally, if you are in a multi-threaded environment, you can use Mutex, as cells are not thread safe. Here is an example of using RefCell to introduce intrerior mutability. (thanks @kmdreko)

As you can see, the data of our ThingMaker was wrapped in a RefCell:

struct ThingMaker {
    data: RefCell<u64>,
}

Which we can then use to get mutable references to our data:

fn process_thing(obj: &Thing) {
    let mut data = &mut *obj.parent.data.borrow_mut();
    *data += 1;
    println!("New data: {}", data);
}

Note that we only need an immutable reference to mutate our object, so we can get away with only storing immutable references inside our Things:

struct Thing<'a> {
    pub parent: &'a ThingMaker,
}
Dizzar
  • 35
  • 3
  • To be fair, both approaches use interior mutability to solve the problem posed in your question. The refcounted smart-pointers add "shared ownership", so that each `Thing` can "own" its maker: this avoids a whole host of lifetime shenanigans that would otherwise arise, but is actually separate from the mutability question posed. – eggyal Jul 07 '22 at 13:53