Cristopher Namchee
Cristopher Namchee

Reputation: 92

How can I split strings without worrying about mutable borrows?

I'm currently trying to solve this challenge to learn Rust. I've created my own solution below:

use std::io;
use std::vec::Vec;
use std::collections::HashSet;

fn main() {
    let stdin = io::stdin();
    let mut buffer = String::new();
    let mut set = HashSet::new();
    let req = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid", "cid"];
    let mut res: u32 = 0;

    while stdin.read_line(&mut buffer).unwrap() > 0 {
        if buffer == "\n" {
            for r in req.iter() {    
                if set.contains(r) {
                    set.remove(r);
                }
            }

            if (set.len() == req.len()) || (set.len() == 7 && !set.contains("cid")) {
                res += 1;
            }
        } else {
            if buffer.ends_with('\n') {
                buffer.pop();
            }

            let inputs: Vec<&str> = buffer.split_whitespace().collect();

            for key_val in inputs {
                let kval: Vec<&str> = key_val.split(':').collect();
                
                set.insert(kval[0]);
            }
        }   
        
        buffer.clear();
    }

    println!("{}", res);
}

When I tried to compile it, the compiler outputs the error:

error[E0502]: cannot borrow `buffer` as mutable because it is also borrowed as immutable

if (set.len() == req.len()) || (set.len() == 7 && !set.contains("cid")) {
   |                 --- immutable borrow later used here
...
28 |             let inputs: Vec<&str> = buffer.split_whitespace().collect();
   |                                     ------ immutable borrow occurs here
...
37 |         buffer.clear();
   |         ^^^^^^^^^^^^^^ mutable borrow occurs here

I've solved other problems by using this kind of style before (using &mut buffer and split it somewhere) without getting this error.

Why does this error occur here and how can I fix it?

Upvotes: 1

Views: 1122

Answers (1)

rodrigo
rodrigo

Reputation: 98338

Look at the type of your set variable. You can get it by writing a set as () just after the set.insert(...) line. You'll see this error message:

non-primitive cast: `HashSet<&str>` as `()`

So you set holds references to str. But where do these references live. Well, all your str ultimately come from buffer so they are references to the contents of buffer. If you were allowed to do buffer.clear() all these references would become invalidated.

You have two basic options:

  1. Keep all your lines in memory all the time so that your references in set are not invalidated. You could use `read
  2. Store String instead of &str values in the set.

The easier solution is #2:

set.insert(kval[0].to_owned());

With that and a few other minor fixes, your algorithm will compile (playground).

Option #1 would require to read the whole stdin into a variable and then iterate over the lines:

    use std::io::Read;
    let mut stdin = io::stdin();
    let mut input = String::new();
    stdin.read_to_string(&mut input).unwrap();
    for buffer in input.lines() {
       ...
    }

Now buffer is a &str instead of a String, but the lifetime of the values in set will be that of input that is outside of the loop. You would need a few minor fixes, particularly with the end-of-line checks.

Upvotes: 3

Related Questions