Search code examples
rustborrow-checker

Value borrow issue in Rust with bufreader


I've searched (and found btw) a lot of issues that are somewhat identical yet I'm struggling to find a way to get code below working in a Rustomatic way. I've made an grep equivalant that works, now I wanted to make it better with bufreader instead of loading whole the text(log file actually). But I have an issue with the borrowchecker that I cannot seem to get working with whatever I'm trying. I've tried with slices but other errors appear so I'm somewhat lost how I can make this work.

use::foo;
use::clap::Parser;
use::std::io::{BufRead,BufReader};
use::std::fs::File;
/// Search for a pattern in a file and display the lines that contain it.
#[derive(Parser)]
struct Cli {
    /// the pattern that we want to search
    #[arg(long)]
    pattern : String,
    /// the path where the file is located in which we want to search the pattern
    #[arg(long)]
    path: std::path::PathBuf,
}

fn main(){
    let args = Cli::parse();
    // let content = std::fs::read_to_string(&args.path).expect("could not read file");
    let content = File::open(&args.path).expect("could not read file");
    let reader = BufReader::new(content);
    let lines = reader.lines();

    for line in lines {
        if line.unwrap().contains(&args.pattern){
            println!("{:?}",line);
        }
    }
}

the error:

error[E0382]: borrow of moved value: `line`
    --> src/main.rs:25:29
     |
23   |     for line in lines {
     |         ---- move occurs because `line` has type `Result<std::string::String, std::io::Error>`, which does not implement the `Copy` trait
24   |         if line.unwrap().contains(&args.pattern){
     |            ---- -------- `line` moved due to this method call
     |            |
     |            help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents
25   |             println!("{:?}",line);
     |                             ^^^^ value borrowed here after move

Solution

  • The reason of your error is following.

    Since you are using BufReader::lines it returns a Lines object. As it is said in the documentation it implements Iterator that yields Result<String, Error> datatype. After .unwrap() you are unwrapping the inner String and call contains in the line if line.unwrap().contains(&args.pattern){ so after that this line-object (that has the type Result<String, Error>) is moved and cannot be used any longer. So you get the mentioned error in println!. This is a typical case with the borrow checker in Rust. So there are at least two ways to resolve it:

    1. Access .unwrap() by a reference (it is possible, because for .contains it is enough to have a reference to the object (see the interface here: contains(&self ...) https://doc.rust-lang.org/std/string/struct.String.html#method.contains). So all you need is calling line.as_ref().unwrap().contains(...) in the if-statement.

    2. Extract the string object once and then perform .contains and println! for it. So you can write something like:

    // The variable `line` is MOVED here, but not `extracted_str`
    let extracted_str = line.unwrap();
    
    // The object extracted_str is NOT moved here because now the compiler understands
    // that `.contains` is accessed it by the reference
    if extracted_str.contains(args.pattern) {
    
        // extracted_str is moved here but you should not care of it
        // because you don't use extracted_str after printing,
        // but if you needed to used it, likely you'd have to clone the string
        // using extracted_str.clone() in the `println!` macros
        println!("{}", extracted_str);
    }
    

    In short, line.unwrap() moves the whole object (because is has the interface unwrap(self) without &) line including the inner string, so you can't use line again below. But if you store the unwrapping string at this step, you can use it many times if the inner methods support &self in the interface (usually it is true for most methods).