Hassle-free conflict of borrowing from an expanded loop
Consider the following code ( Playpen ), which is designed to easily parse input from stdin and place each line and link to that line in a structure:
use std::io;
use std::io::BufRead;
struct Line<'a> {
text: Box<String>,
column: &'a str,
}
fn main() {
let column = 1;
let stdin = io::stdin();
let mut lines: Vec<Line> = Vec::new();
for line_res in stdin.lock().lines() {
lines.push(Line {
text: Box::new(line_res.unwrap()),
column: "",
});
let line = lines.last_mut().unwrap();
line.column = line.text.split_whitespace().nth(column)
.unwrap_or("");
}
}
That is, it Line::column
must refer to Line::text
. Note that I first set column
to ""
(and changed it later) because I don't know how to reference the element text
at creation time.
Unfortunately, the above code does not compile, spitting out the following very dumb error message:
<anon>:15:3: 15:8 error: cannot borrow `lines` as mutable more than once at a time
<anon>:15 lines.push(Line {
^~~~~
note: in expansion of for loop expansion
<anon>:14:2: 23:3 note: expansion site
<anon>:20:14: 20:19 note: previous borrow of `lines` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `lines` until the borrow ends
<anon>:20 let line = lines.last_mut().unwrap();
^~~~~
note: in expansion of for loop expansion
<anon>:14:2: 23:3 note: expansion site
<anon>:24:2: 24:2 note: previous borrow ends here
<anon>:9 fn main() {
...
<anon>:24 }
^
<anon>:20:14: 20:19 error: cannot borrow `lines` as mutable more than once at a time
<anon>:20 let line = lines.last_mut().unwrap();
^~~~~
note: in expansion of for loop expansion
<anon>:14:2: 23:3 note: expansion site
<anon>:20:14: 20:19 note: previous borrow of `lines` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `lines` until the borrow ends
<anon>:20 let line = lines.last_mut().unwrap();
^~~~~
note: in expansion of for loop expansion
<anon>:14:2: 23:3 note: expansion site
<anon>:24:2: 24:2 note: previous borrow ends here
<anon>:9 fn main() {
...
<anon>:24 }
^
<anon>:20:14: 20:19 error: cannot borrow `lines` as mutable more than once at a time
<anon>:20 let line = lines.last_mut().unwrap();
^~~~~
note: in expansion of for loop expansion
<anon>:14:2: 23:3 note: expansion site
<anon>:20:14: 20:19 note: previous borrow of `lines` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `lines` until the borrow ends
<anon>:20 let line = lines.last_mut().unwrap();
^~~~~
note: in expansion of for loop expansion
<anon>:14:2: 23:3 note: expansion site
<anon>:24:2: 24:2 note: previous borrow ends here
<anon>:9 fn main() {
...
<anon>:24 }
^
error: aborting due to 3 previous errors
Of course, this is nonsense! By the way, the line conflicts with itself. The only clue I can see in this error is the fact that the loop is unwrapping. However, shouldn't all the workpieces made in the loop expire at the end of each iteration?
What is the actual semantic issue with the above code and what is the fix?
source to share
In Rust it is not possible to have a reference to something in the same structure.
Think about it:
struct Line<'a> {
text: Box<String>,
column: &'a str,
}
What is it 'a
? Lifetime of the field text
(by the way, wrap Box
around a String
is redundant). Thus, you cannot express a type until it already exists.
If such a link is allowed, you face problems like this:
let mut line = Line { text: "foo".to_owned(), column: "" };
line.column = &self.text;
line.text = "bar".to_owned();
// Uh oh, column is now invalid, pointing to freed memory
There is no way around this while the two values ββare stored together; they must be kept separately. The workaround that is most likely suitable for your case is to store the indexes, for example. start and end indices as (usize, usize)
.
Now: why are these specific errors? It boils down to what 'a
is displayed as; your vector of strings Vec<Lines<'x>>
for one lifetime 'x
: each instance Lines
has the same lifetime. This means that the estimated lifetime must be greater than the estimated cycle time, and therefore each iteration of the loop does indeed keep the volatile reference alive, and therefore the line actually conflicts with itself (or rather the previous iteration) in this way. The cycle does not unfold - its simply borrowings from the cycle are really alive.
source to share