-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
borrow scopes should not always be lexical #6393
Comments
nominating for production ready |
I would call this either well-defined or backwards-compat. |
Examples from Jack [09:57:06] https://github.com/mozilla/servo/blob/master/src/components/servo/layout/box_builder.rs#L387-L411 |
The code got reorganized, but here is the specific appeasement of the borrow checker i had to do: |
After some discussion, it's clear that the real problem here is that the borrow checker makes no effort to track aliases, but rather always relies on the region system to determine when a borrow goes out of scope. I am reluctant to change this, at least not in the short term, because there are a number of other outstanding issues I'd like to address first and any changes would be a significant modification to the borrow checker. See issue #6613 for another related example and a somewhat detailed explanation. |
I wonder if we could improve the error messages to make it more clear what's going on? Lexical scopes are relatively easy to understand, but in the examples of this issue that I've stumbled across it was by no means obvious what was going on. |
just a bug, removing milestone/nomination. |
accepted for far future milestone |
triage bump |
I've had some thoughts about the best way to fix this. My basic plan is that we would have a notion of when a value "escapes". It will take some work to formalize that notion. Basically, when a borrowed pointer is created, we will then track whether it has escaped. When the pointer is dead, if it is has not escaped, this can be considered to kill the loan. This basic idea covers cases like "let p = &...; use-p-a-bit-but-never-again; expect-loan-to-be-expired-here;" Part of the analysis will be a rule that indicates when a return value that contains a borrowed pointer can be considered not to have escaped yet. This would cover cases like "match table.find(...) { ... None => { expect-table-not-to-be-loaned-here; } }" The most interesting part of all this is the escaping rules, of course. I think that the rules would have to take into account the formal definition of the function, and in particular to take advantage of the knowledge bound lifetimes give us. For example, most escape analyses would consider a pointer
then in fact we know that
So presumably the escaping rules would have to consider whether the bound lifetime appeared in a mutable location or not. This is effectively a form of type-based alias analysis. Similar reasoning I think applies to function return values. Hence
The reason here is that because Another way to think about this is not that the loan is expired early, but rather that the scope of a loan begins (typically) as tied to the borrowed variable and not the full lifetime, and is only promoted to the full lifetime when the variable escapes. Reborrows are a slight complication, but they can be handled in various ways. A reborrow is when you borrow the contents of a borrowed pointer -- they happen all the time because the compiler inserts them automatically into almost every method call. Consider a borowed pointer Anyway, more thought is warranted, but I'm starting to see how this could work. I'm still reluctant to embark on any extensions like this until #2202 and #8624 are fixed, those being the two known problems with borrowck. I'd also like to have more progress on a soundness proof before we go about extending the system. The other extension that is on the timeline is #6268. |
I believe I've run into this bug. My use-case and work-around attempts: |
Here's another example of this bug (I think): use std::util;
enum List<T> {
Cons(T, ~List<T>),
Nil
}
fn find_mut<'a,T>(prev: &'a mut ~List<T>, pred: |&T| -> bool) -> Option<&'a mut ~List<T>> {
match prev {
&~Cons(ref x, _) if pred(x) => {}, // NB: can't return Some(prev) here
&~Cons(_, ref mut rs) => return find_mut(rs, pred),
&~Nil => return None
};
return Some(prev)
} I'd like to write: fn find_mut<'a,T>(prev: &'a mut ~List<T>, pred: |&T| -> bool) -> Option<&'a mut ~List<T>> {
match prev {
&~Cons(ref x, _) if pred(x) => return Some(prev),
&~Cons(_, ref mut rs) => return find_mut(rs, pred),
&~Nil => return None
}
} reasoning that the |
I've had more thoughts about how to code this up. My basic plan is that for each loan there would be two bits: an escaped version and a non-escaped version. Initially we add the non-escaped version. When a reference escapes, we add the escaped bits. When a variable (or temporary, etc) goes dead, we kill the non-escaped bits -- but leave the escaped bits (if set) untouched. I believe this covers all major examples. |
cc @flaper87 |
Does this issue cover this?
|
cc me |
Good examples in #9113 |
cc me |
I could be mistaken, but the following code seems to be hitting this bug as well:
Ideally, the mutable borrow caused by calling set_val would end as soon as the function returns. Note that removing the 'int_ref' field from the struct (and associated code) causes the issue to go away. The behavior is inconsistent. |
For the case: "let p = &...; use-p-a-bit-but-never-again; expect-loan-to-be-expired-here;" I would find acceptable for now a kill(p) instruction to manually declare end of scope for that borrow. Later versions could simply ignore this instruction if it is not needed or flag it as an error if re-use of p is detected after it. |
/* (wanted) */
/*
fn main() {
let mut x = 10;
let y = &mut x;
println!("x={}, y={}", x, *y);
*y = 11;
println!("x={}, y={}", x, *y);
}
*/
/* had to */
fn main() {
let mut x = 10;
{
let y = &x;
println!("x={}, y={}", x, *y);
}
{
let y = &mut x;
*y = 11;
}
let y = &x;
println!("x={}, y={}", x, *y);
} |
There's the drop() method in the prelude that does that. But doesn't seem On Sun, Apr 5, 2015, 1:41 PM axeoth [email protected] wrote:
|
Closing in favor of rust-lang/rfcs#811 |
@metajack the link for your original code is a 404. Can you include it inline for people reading this bug? |
After some digging, I believe this is equivalent to the original code: |
Or rather, that's the workaround I used when I filed this bug. The original code before that change seems to be this: I'm not sure how relevant these specific examples are now since they were pre-Rust 1.0. |
@metajack it would be great to have an ultra simple (post 1.0) example in the top of this issue. This issue is now part of rust-lang/rfcs#811 |
fn main() {
let mut nums=vec![10i,11,12,13];
*nums.get_mut(nums.len()-2)=2;
} |
I think what I was complaining about was something like this: That particular case appears to work now. |
@vitiral An example in today's Rust that I believe applies: fn main() {
let mut vec = vec!();
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
} The Curiously, if you don't try to capture |
@wyverland oh ya, I hit that just yesterday, pretty annoying. @metajack can you edit the first post to include that example? |
It's not yet hit nightly, but I just want to say that this now compiles: #![feature(nll)]
fn main() {
let mut vec = vec!();
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
} |
…for non-lexical lifetimes)
If you borrow immutably in an
if
test, the borrow lasts for the wholeif
expression. This means that mutable borrows in the clauses will cause the borrow checker to fail.This can also happen when borrowing in the match expression, and needing a mutable borrow in one of the arms.
See here for an example where the if borrows boxes, which causes the nearest upwards
@mut
to freeze. Thenremove_child()
which needs to borrow mutably conflicts.https://github.com/mozilla/servo/blob/master/src/servo/layout/box_builder.rs#L387-L411
Updated example from @Wyverald
The text was updated successfully, but these errors were encountered: