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

fn main() {
    let mut buffer: String = String::from("value_");
    let mut parameter_value = String::with_capacity(7);
    let mut parameter_values: Vec<&str> = Vec::with_capacity(5);
    for c in "test!".chars() {
        buffer.push(c);
        parameter_value.push_str(buffer.as_str());
        parameter_values.push(parameter_value.as_str());
        parameter_value.clear();
        buffer = String::from("value_");
    }
    println!("OK?!")
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `parameter_value` as mutable because it is also borrowed as immutable
 --> src/main.rs:7:9
  |
7 |         parameter_value.push_str(buffer.as_str());
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
8 |         parameter_values.push(parameter_value.as_str());
  |         ----------------      --------------- immutable borrow occurs here
  |         |
  |         immutable borrow later used here

error[E0502]: cannot borrow `parameter_value` as mutable because it is also borrowed as immutable
 --> src/main.rs:9:9
  |
8 |         parameter_values.push(parameter_value.as_str());
  |         ----------------      --------------- immutable borrow occurs here
  |         |
  |         immutable borrow later used here
9 |         parameter_value.clear();
  |         ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` (bin "playground") due to 2 previous errors

I want to parse some text and filter different names with their value(s), store them all as &str in some structs later on. Both parameter_value and buffer need to be initialized here at the end of each loop.

But right now I try to get my head around borrowing and got stuck with this:

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

The link to error[E0502] suggests changing borrow-sequence to parameter_values.push(parameter_value.as_str()) (immutable borrow parameter_value) before parameter_value.push_str(buffer.as_str()) (mutable borrow parameter_value)

But temporary parameter_value has always to be build first before it could even be moved into parameter_values.

As I got it the compiler needs at least the same level of ownership. And here parameter_value is mutated before it gets copied so level of borrowing drops.

From my (beginners) point of view there are two options:

  1. I change parameter_values.push(parameter_value.as_str()) to a mutable borrow of parameter_value - though I doubt that this is possible.
  2. I make the compiler ignore this level-drop. Either RefCell<Vec<&str>> or Mutex<Vec<&str>> should achieve this.

Am I right?

Neither of your options seems plausible.

The problem in your code is that &str is a reference without ownership, so it always needs to be a borrow – i.e. some view into data that also still exists somewhere else.

With

parameter_values.push(parameter_value.as_str());

you create a &str that’s a view into the data of parameter_value. Afterwards however, you keep further modifying parameter_value (even clearing it, though the precise nature of mutation isn’t really relevant to the borrow checker) which can (and does in your case) invalidate the &str borrow you’ve created.

The best “beginner” fix in your case is probably to replace &str with an owned String type, e.g.

fn main() {
    let mut buffer: String = String::from("value_");
    let mut parameter_value = String::with_capacity(7);
-   let mut parameter_values: Vec<&str> = Vec::with_capacity(5);
+   let mut parameter_values: Vec<String> = Vec::with_capacity(5);
    for c in "test!".chars() {
        buffer.push(c);
        parameter_value.push_str(buffer.as_str());
-       parameter_values.push(parameter_value.as_str());
+       parameter_values.push(parameter_value.clone());
        parameter_value.clear();
        buffer = String::from("value_");
    }
    println!("OK?!")
}
1 Like

You need to remember that &str doesn't hold any data. It's only a temporary view into string data of some other object like String. There's no garbage collector in Rust, so that data will not be kept alive by &str, and can be destroyed and invalidate &str.

This code is an actual bug that would cause a crash due to potential use-after-free, or garbage results in the best case:

parameter_values.push(parameter_value.as_str());
parameter_value.clear();

Data in parameter_values becomes invalid when you call clear, because you haven't put a new string in parameter_values, you've only let it temporarily view data that is stored in parameter_value, and then you cleared that data, invalidating parameter_values's content.

1 Like