Search code examples
rustborrow-checkerownership

How do I iterate over a vector of strings stored in a struct without moving them?


I have a large struct that has a vector of Strings 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?


Solution

  • 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.