Reputation: 583
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 Item
s 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
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
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