Reputation: 43
I'm new to Rust and trying to understand how to properly handle the following borrow checker "problem":
I have a struct containing some fields, and I would like some of the fields to be constant over the lifetime of the object (const in C++). This way, I could borrow &mut self while also holding an immutable reference to the const field.
Here is a minimal example:
use std::collections::HashMap;
struct Process {
const_data: HashMap<String, i32>, // this is supposed to never be changed after construction!
mut_data: i32, // this is supposed to be mutated during object lifetime
}
impl Process {
fn do_more(&mut self, num: &i32) {
self.mut_data += num;
}
fn do_somethig(&mut self, name: &str) {
let num = self.const_data.get(name).unwrap(); // reference to part of self.const_data
self.do_more(num); // borrowing &mut self
}
}
fn main() {
let mut p = Process { const_data: HashMap::from([
("foo".to_string(), 1),
("bar".to_string(), 2),
("buzz".to_string(), 3),
]), mut_data: 0 };
p.do_somethig("foo");
}
It fails to compile with the error message
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:16:9
|
15 | let num = self.const_data.get(name).unwrap();
| ------------------------- immutable borrow occurs here
16 | self.do_more(num);
| ^^^^^-------^^^^^
| | |
| | immutable borrow later used by call
| mutable borrow occurs here
Is there a way to mark Process::const_data
as immutable, such that the borrow checker knows that its not contradicting the "one mutable reference" principle?
PS: In the example above we could make Process::do_more()
take num
by value and not by reference, however, in my actual project const_data
would contain a complex struct which should not be copied or moved.
Upvotes: 0
Views: 885
Reputation: 387
You can use Rc to wrap your const_data
. Rc::clone
is a cheap operation.
use std::{collections::HashMap, rc::Rc};
struct Process {
const_data: Rc<HashMap<String, i32>>, // this is supposed to never be changed after construction!
mut_data: i32, // this is supposed to be mutated during object lifetime
}
impl Process {
fn do_more(&mut self, num: &i32) {
self.mut_data += num;
}
fn do_somethig(&mut self, name: &str) {
let map = Rc::clone(&self.const_data);
let num = map.get(name).unwrap(); // reference to part of self.const_data
self.do_more(num); // borrowing &mut self
}
}
fn main() {
let mut p = Process {
const_data: Rc::new(HashMap::from([
("foo".to_string(), 1),
("bar".to_string(), 2),
("buzz".to_string(), 3),
])),
mut_data: 0,
};
p.do_somethig("foo");
}
Upvotes: 1
Reputation: 23244
You can use a RefCell
to store the mutable portion of the struct, and then take a non-mutable reference to self
:
use std::cell::RefCell;
use std::collections::HashMap;
struct Process {
const_data: HashMap<String, i32>, // this is supposed to never be changed after construction!
mut_data: RefCell<i32>, // this is supposed to be mutated during object lifetime
}
impl Process {
fn do_more(&self, num: &i32) {
*self.mut_data.borrow_mut() += num;
}
fn do_somethig(&self, name: &str) {
let num = self.const_data.get(name).unwrap(); // reference to part of self.const_data
self.do_more(num); // borrowing &mut self
}
}
fn main() {
let p = Process { const_data: HashMap::from([
("foo".to_string(), 1),
("bar".to_string(), 2),
("buzz".to_string(), 3),
]), mut_data: RefCell::new (0) };
p.do_somethig("foo");
}
PS: For an i32
you would probably use Cell
instead of RefCell
, but I'm assuming that mut_data
is really a more complex type where RefCell
is justified.
Upvotes: 1
Reputation: 410
The reason your code doesn't work is because the borrow checker doesn't check if the &mut Process
borrow actually modifies what part of the data. It just sees multiple borrows with one of them being mutable and that's not allowed.
The solution is to attach the do_more
method directly to mut_data
, like so:
use std::collections::HashMap;
#[derive(Debug)]
struct MutData {
data: i32
}
impl MutData {
fn do_more(&mut self, num: &i32) {
self.data += num;
}
}
struct Process {
const_data: HashMap<String, i32>, // this is supposed to never be changed after construction!
mut_data: MutData, // this is supposed to be mutated during object lifetime
}
impl Process {
fn do_more(&mut self, num: &i32) {
self.mut_data.do_more(num);
}
fn do_somethig(&mut self, name: &str) {
let num = self.const_data.get(name).unwrap(); // reference to part of self.const_data
self.mut_data.do_more(num); // borrowing &mut self.mut_data
}
}
fn main() {
let const_data = HashMap::from([
("foo".to_string(), 1),
("bar".to_string(), 2),
("buzz".to_string(), 3),
]);
let mut p = Process { const_data, mut_data: MutData { data: 0 } };
println!("{:?}", p.mut_data);
p.do_somethig("bar");
println!("{:?}", p.mut_data);
}
Upvotes: 0
Reputation: 998
You need to copy your value. This should work:
use std::collections::HashMap;
struct Process {
const_data: HashMap<String, i32>,
mut_data: i32,
}
impl Process {
fn do_more(&mut self, num: &i32) {
self.mut_data += num;
}
fn do_somethig(&mut self, name: &str) {
let ref num = self.const_data[name].clone(); // Reference to value similar to *self.const_data.index(name)
self.do_more(num);
}
}
fn main() {
let mut p = Process { const_data: HashMap::from([
("foo".to_string(), 1),
("bar".to_string(), 2),
("buzz".to_string(), 3),
]), mut_data: 0 };
p.do_somethig("foo");
}
Upvotes: 0