0

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!

3
  • What does the handle_character method actually do? Does it need access to self.content? If so, you fundamentally have a memory conflict in what you're trying to do; if not, is it really necessary that handle_character be defined on the Self type rather than some field thereof (which could itself be a type that combines more than one of the current fields)? Your design choices here probably are not well suited to the actual data flow, which idiomatic Rust strongly prefers. Commented Oct 29, 2024 at 18:58
  • handle_character has to mutate the has_error field depending on some condition. Commented Oct 29, 2024 at 19:03
  • Turning a function into a macros can sometimes help. Commented Oct 30, 2024 at 0:37

2 Answers 2

1

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 }
*/
Sign up to request clarification or add additional context in comments.

Comments

0

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.