ddumont
ddumont

Reputation: 583

How can I mutate fields of a struct while referencing other fields?

Here's an example of a problem I ran into:

pub struct Item {
  name: String,
  value: LockableValue, // another struct that I'd like to mutate
}
impl Item {
  pub fn name(&self) -> &str {
    &self.name
  }

  pub fn value_mut(&mut self) -> &mut LockableValue {
    &self.value
  }
}

pub fn update(item: &mut Item) {
  let value = item.value_mut();
  value.change(); // how it changes is unimportant
  println!("Updated item: {}", item.name());
}

Now, I know why this fails. I have a mutable reference to item through the mutable reference to the value.

If I convert the reference to an owned String, it works fine, but looks strange to me:

pub fn update(item: &mut Item) {
  let name = { item.name().to_owned() };

  let value = item.value_mut();
  value.change(); // how it changes is unimportant
  println!("Updated item: {}", name); // It works!
}

If I let value reference drop, then everything is fine.

pub fn update(item: &mut Item) {
  {
    let value = item.value_mut();
    value.change(); // how it changes is unimportant
  }
  println!("Updated item: {}", item.name()); // It works!
}

The value.change() block is rather large, and accessing other fields in item might be helpful. So while I do have solutions to this issue, I'm wondering if there is a better (code-smell) way to do this. Any suggestions?

My intention behind the above structs was to allow Items to change values, but the name should be immutable. LockableValue is an tool to interface with another memory system, and copying/cloning the struct is not a good idea, as the memory is managed there. (I implement Drop on LockableValue to clean up.)

I was hoping it would be straight-forward to protect members of the struct from modification (even if it were immutable) like this... and I can, but it ends up looking weird to me. Maybe I just need to get used to it?

Upvotes: 0

Views: 1336

Answers (2)

user8348425
user8348425

Reputation: 51

I don't think you need to use RefCell here. see borrowing

The fields of a struct or a reference to a struct are treated as separate entities when borrowing

so you should be able to mut borrow value while having a immutable reference to name. This works when I change name type to String.

pub struct Item {
    name: String,
    value: String, // another struct that I'd like to mutate
}
impl Item {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn value_mut(&mut self) -> &mut String {
        &mut self.value
    }
}

pub fn update(item: &mut Item) {
    let value = item.value_mut();
    let _ = value.insert(0, 'a'); // insert take a &mut String
    println!("Updated item: {}", item.name());
}

So I suspect there is some property of LockableValue that is different here. Or you may be using a very old version of Rust. Maybe you can give a little more details?

Upvotes: 0

cafce25
cafce25

Reputation: 27549

You could use interior mutability on only the part that you want to mutate by using a RefCell like ths:

use std::cell::{RefCell, RefMut};
pub struct LockableValue;
impl LockableValue {
    fn change(&mut self) {}
}
pub struct Item {
    name: String,
    value: RefCell<LockableValue>, // another struct that I'd like to mutate
}
impl Item {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn value_mut(&self) -> RefMut<'_, LockableValue> {
        self.value.borrow_mut()
    }
}

pub fn update(item: &Item) {
    let name = item.name();
    let mut value = item.value_mut();
    value.change(); // how it changes is unimportant
    println!("Updated item: {}", name);
}

That way you only need a shared reference to Item and you don't run into an issue with the borrow checker.

Not that this forces the borrow checks on value to be done at runtime though and thus comes with a performance hit.

Upvotes: 1

Related Questions