I have a large struct that has a vector of String
s as a member. I am trying to iterate over these and pass each to another method that takes a &str
argument. The latter method requires &mut self
in order to process an input stream, and I can't seem to reconcile the borrows.
I boiled this down to a simple (?) example that shows the issue I have. See below, and also a link to a the code on the Rust playground
pub struct Configuration {
pub prefixes: Vec<String>,
}
impl Configuration {
pub fn check_one(&mut self, prefix: &str) -> bool {
"testy".starts_with(prefix)
}
pub fn check(&mut self) -> bool {
for prefix in &self.prefixes {
if self.check_one(prefix) {
return true;
}
}
return false;
}
}
fn main() {
let mut config = Configuration{ prefixes: vec!["fl".to_string()], };
dbg!(config.check());
}
The actual question. If I make the argument a reference (self.check_one(&prefix)
) then I get an issue that self.prefixes
is moved by into_iter()
, and the compiler recommends &self.prefixes
. Fine. But if I try that, I'm told that I cannot borrow self
immutably when I have already borrowed it mutably, and I cannot seem to reconcile these two.
Bottom line is that I need to pass each String
in a member vector to a method to check. I don't want the string or vector to be modified, but I have to borrow &self
as mutable. Suggestions?
There is no way to call a method that takes &mut self
when part of self
is borrowed. That's because a method taking &mut self
is allowed to modify any part of self
. You can't make a method that's allowed to modify only a part of self
.
As written, check_one
should take &self
. Inside check
, &mut self
will automatically convert to &self
on calling check_one
.
pub fn check_one(&self, prefix: &str) -> bool {
"testy".starts_with(prefix)
}
If you need to be able to modify all parts of self
, including prefixes
, then you need to clone prefixes
so that the copy self
owns is not borrowed.
for prefix in &self.prefixes.clone() {
if self.check_one(prefix) {
return true;
}
}
If you don't need mutable or immutable access to prefixes
, then you can do one of two things. First, you could make a regular function that takes the other parts of self
.
// Replace `()` with the other parts of `self`
pub fn check_one(_other_parts: (), prefix: &str) -> bool {
"testy".starts_with(prefix)
}
for prefix in &self.prefixes {
if check_one((), prefix) {
return true;
}
}
Or you can temporarily take prefixes
from self
, and put it back when done.
pub fn check(&mut self) -> bool {
let prefixes = std::mem::take(&mut self.prefixes);
for prefix in &prefixes {
if self.check_one(prefix) {
self.prefixes = prefixes;
return true;
}
}
self.prefixes = prefixes;
return false;
}
Lastly, a less common case is when check_one
may or may not modify prefixes
depending on the data. In that case, in order to only clone prefixes
when necessary, you can use Rc
or Arc
. This is a more extensive change.
pub struct Configuration {
pub prefixes: Rc<Vec<String>>,
}
impl Configuration {
pub fn check_one(&mut self, prefix: &str) -> bool {
let check = "testy".starts_with(prefix);
// Do some mutable work
if check {
Rc::make_mut(&mut self.prefixes).push("new".to_string());
}
return check;
}
pub fn check(&mut self) -> bool {
// The only change needed here is a dereference
for prefix in &*self.prefixes.clone() {
if self.check_one(prefix) {
return true;
}
}
return false;
}
}
When you clone the Rc
itself, it creates a second read-only reference that points to the same data. Calling make_mut
will clone the data when there is more than one reference alive, giving you mutable access to the data. In this case, the first time make_mut
is called, the data will be cloned, the Rc
will replace itself with a new Rc
containing the cloned data, and a mutable reference will be returned. If make_mut
is called again in the same loop, it will simply return a mutable reference, since it's no longer linked to the other Rc
.