EDIT: Go here for a smaller example.
Code:
#![feature(nll)]
extern crate clang;
use std::collections::HashMap;
use std::error::Error;
use clang::*;
#[derive(Default)]
struct SymbolDesc<'a> {
deps: Vec<Entity<'a>>,
}
fn visit<'a>(_entity: Entity<'a>, _desc: &mut SymbolDesc<'a>) {
// snip
}
fn main() -> Result<(), Box<Error>> {
let clang = Clang::new()?;
let index = Index::new(&clang, false, false);
let sources = vec!["examples/simple.c", "examples/simple_impl.c"];
let mut tus = vec![];
let mut sym_table: HashMap<String, SymbolDesc<'_>> = HashMap::new();
for source in sources {
tus.push(index.parser(source).parse()?);
for child in tus.last().unwrap().get_entity().get_children() {
if let Some(child_name) = child.get_name() {
let desc = sym_table.entry(child_name).or_default();
visit(child, desc);
}
}
}
Ok(())
}
Error:
error[E0502]: cannot borrow `tus` as mutable because it is also borrowed as immutable
--> src/main.rs:28:9
|
28 | tus.push(index.parser(source).parse()?);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
29 |
30 | for child in tus.last().unwrap().get_entity().get_children() {
| --- immutable borrow occurs here
31 | if let Some(child_name) = child.get_name() {
32 | let desc = sym_table.entry(child_name).or_default();
| --------- borrow used here in later iteration of loop
The crux of the problem is that at line visit(child, desc), table starts borrowing tu because it has type HashMap<String, SymbolDesc<'tu>>. This prevents tus.push(index.parser(source).parse()?) because it needs a mutable borrow.
I wish the error message mentioned that table borrows tu because that's far from obvious. But once you see it, it's understandable why the borrow checker is not happy.
Note for team: tried it locally; the error message is the same with and without #![feature(nll)].
I've added to the RC2 milestone but I consider this a "nice to have". That said, I've been wanting for a while to have borrows be able to explain better how the reference came to be used at a particular spot — this overlaps with other sorts of region errors that could make use of a similar trace. It'd be helpful to brainstorm what we think the error should look like, I think.
Can we maybe get a standalone example that doesn't use clang?
@nikomatsakis Here's a smaller example (playground link):
#![feature(nll)]
struct Parent;
struct Child<'a>(Option<&'a Parent>);
// the signature implies that the child borrows the parent
fn adopt<'a>(p: &'a Parent, c: &mut Child<'a>) {
c.0 = Some(p);
}
fn main() {
let mut parents: Vec<Parent> = vec![];
let mut children: Vec<Child<'_>> = vec![];
for _ in 0..2 {
parents.push(Parent);
let p = parents.last().unwrap();
children.push(Child(None));
let c = children.last_mut().unwrap();
adopt(p, c); // comment this line to make the program compile
}
}
Error:
error[E0502]: cannot borrow `parents` as mutable because it is also borrowed as immutable
--> src/main.rs:16:9
|
16 | parents.push(Parent);
| ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
17 | let p = parents.last().unwrap();
| ------- immutable borrow occurs here
18 |
19 | children.push(Child(None));
| -------- immutable borrow used here, in later iteration of loop
Removing adopt(p, c); will make the program compile, but the error message doesn't mention that line at all.
It's important to note that, even though in this example it's fairly obvious that children borrow parents, in the original post it was not.
Removing from the Rust 2018 milestone -- this is not a Rust 2018 blocker.
NLL triage. Made title more informative. P-medium.
Diagnostics triage: we believe this issue can be resolved by explaining why something is being borrowed instead of just where. Will need some digging in the NLL diagnostics machinery.
Most helpful comment
Diagnostics triage: we believe this issue can be resolved by explaining why something is being borrowed instead of just where. Will need some digging in the NLL diagnostics machinery.