Sasha Zoria
Sasha Zoria

Reputation: 771

Rust Borrow Checker Error: "cannot borrow *self as mutable because it is also borrowed as immutable"

I'm working on a Rust function that scans through the lines of a string and processes each character. The code below attempts to iterate through the lines of self.content and calls a mutable function self.handle_character on each character. However, I’m encountering a borrow checker error:

cannot borrow *self as mutable because it is also borrowed as immutable

pub fn run_scanner(&mut self) {
        for (line_number, line) in self.content.lines().enumerate() {
            let mut chars = line.chars();
            while let Some(character) = chars.next() {
                match self.handle_character(&character, &mut chars, line_number) {
                    Ok(_) => {},
                    Err(_) => break
                }
            }
        }
    }
fn handle_character(&mut self, current_char: &char, chars: &mut Chars, line_number: usize) -> Result<(), ()> {

What I've Tried

  1. Collecting Lines to Vector: I tried collecting self.content.lines() into a Vec and then iterating over the vector, but the error persists.

  2. Accessing Lines by Index: I attempted using self.content.lines().nth(line_number) within the loop to avoid holding an iterator borrow on self. However, this solution seems inefficient, especially with larger content, and I'm still encountering issues.

How can I resolve this borrowing conflict in Rust? Ideally, I'd like to iterate over self.content while allowing mutable access to self inside handle_character without running into the borrow checker error.

Any insights on efficient solutions or alternative approaches would be greatly appreciated!

Upvotes: 0

Views: 97

Answers (2)

prog-fh
prog-fh

Reputation: 16925

First, let's try to find the origin of the problem.
In run_scanner(), self.contents.lines() references self.content.
line.chars() references line (emitted by lines()), thus references indirectly self.content.
Passing chars to handle_character() if then like passing a reference to self.content.
But, since this function can mutate the structure as a whole (via &mut self), we are in a situation where self.content is referenced via the chars parameter while potentially mutated via the self parameter.
Rust's borrow checker works exactly in order to prevent these ambiguous situations.

A solution (if suitable for your problem) would be to make chars independent of self in handle_character().
In the example below, we take content out of the structure, work with it, then put it back into the structure.
The std::mem::take() does not imply any copy; it's just pointer manipulation.

use std::str::Chars;

#[derive(Debug)]
struct Scanner {
    content: String,
    dummy: usize,
}
impl Scanner {
    pub fn run_scanner(&mut self) {
        let content = std::mem::take(&mut self.content);
        // for (line_number, line) in self.content.lines().enumerate() {
        for (line_number, line) in content.lines().enumerate() {
            let mut chars = line.chars();
            while let Some(character) = chars.next() {
                match self.handle_character(
                    &character,
                    &mut chars,
                    line_number,
                ) {
                    Ok(_) => {}
                    Err(_) => break,
                }
            }
        }
        self.content = content;
    }
    fn handle_character(
        &mut self,
        current_char: &char,
        chars: &mut Chars,
        _line_number: usize,
    ) -> Result<(), ()> {
        // something stupid, just in order to mutate self
        self.dummy += chars.filter(|c| c == current_char).count();
        Ok(())
    }
}

fn main() {
    let mut s = Scanner {
        content: "something\nelse\n".to_owned(),
        dummy: 0,
    };
    println!("before: {:?}", s);
    s.run_scanner();
    println!("after: {:?}", s);
}
/*
before: Scanner { content: "something\nelse\n", dummy: 0 }
after: Scanner { content: "something\nelse\n", dummy: 1 }
*/

Upvotes: 1

Jmb
Jmb

Reputation: 23319

Assuming self.content is a String, and assuming that handle_character doesn't need to access self.content directly, you can take self.content out of self while processing and put it back afterwards:

pub fn run_scanner (&mut self) {
    let content = std::mem::take (&mut self.content);
    for (line_number, line) in content.lines().enumerate() {
        let mut chars = line.chars();
        while let Some (character) = chars.next() {
            match self.handle_character (&character, &mut chars, line_number) {
                Ok(_) => {},
                Err(_) => break
            }
        }
    }
    self.content = content;
}

⚠ Careful that you don't return in the middle of the loop without putting content back!

Upvotes: 0

Related Questions