Two mutable borrows happen on the same line?

623 views Asked by At

I'm trying to use the Snowball stemmer crate in Rust to stem a vector of words. It should be simple, but the borrow checker keeps rejecting my code:

// Read user input
let input = stdin();
let mut stemmer = Stemmer::new("english").unwrap();
for line in input.lock().lines() {
    let line = line.unwrap();
    let mut query: Vec<_> = line.split_whitespace().collect();
    for t in &mut query {
        *t = stemmer.stem_str(t);
    }
    // …
}

The borrow checker says I have two mutable borrows of stemmer on the line *t = stemmer.stem_str(t); and rejects my code. (Line 80 is where the block of for line in input.lock().lines() end.)

57  18 error    E0499  cannot borrow `stemmer` as mutable more than once at a time (first mutable borrow occurs here) (rust-cargo)
57  18 error    E0499  cannot borrow `stemmer` as mutable more than once at a time (second mutable borrow occurs here) (rust-cargo)
80   5 info     E0499  first borrow ends here (rust-cargo)

If I call the stem() method directly, I get a String, but then I can't just call as_str() and expect to assign the obtained &str back to *t, since the borrow checker complains that the "borrowed value does not live long enough".

57  18 error           borrowed value does not live long enough (temporary value created here) (rust-cargo)
57  18 info            consider using a `let` binding to increase its lifetime (rust-cargo)
57  42 info            temporary value only lives until here (rust-cargo)
80   5 info            temporary value needs to live until here (rust-cargo)

I'm not sure if this has something to do with the implementation details of this library, but I really feel stuck here. I never expected stemming a vector of inputs would be so difficult.

1

There are 1 answers

4
Shepmaster On BEST ANSWER

From the documentation of stem_str:

The str reference it returns is only valid as long as you don't call stem or stem_str again; thus, Rust's borrowchecker won't let call one of them function if you have such a reference in scope.

Presumably, this is because the stemmer implementation actually has some kind of internal buffer where the word is stored as it is stemmed.

This is why you cannot call stem_str twice while keeping a reference to the string; doing so would invalidate the first string!.

I can't just call as_str() and expect to assign the obtained &str back to *t

The compiler is absolutely correct again. You are attempting to create a value, take a reference to it, store the reference, then drop the value! That's a memory vulnerability and you can't do it.

Instead, collect a vector of Strings:

for line in input.lock().lines() {
    let line = line.unwrap();
    let mut query: Vec<_> = line.split_whitespace()
        .map(|t| stemmer.stem(t))
        .collect();
}

I'd highly recommend reading The Rust Programming Language and understanding how references work and what they prevent. Do this before and during getting into anything complicated with ownership. These chapters specifically: