-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Issue #3511 - Rationalize temporary lifetimes. #11585
Issue #3511 - Rationalize temporary lifetimes. #11585
Conversation
Major changes: - Define temporary scopes in a syntax-based way that basically defaults to the innermost statement or conditional block, except for in a `let` initializer, where we default to the innermost block. Rules are documented in the code, but not in the manual (yet). See new test run-pass/cleanup-value-scopes.rs for examples. - Refactors Datum to better define cleanup roles. - Refactor cleanup scopes to not be tied to basic blocks, permitting us to have a very large number of scopes (one per AST node). - Introduce nascent documentation in trans/doc.rs covering datums and cleanup in a more comprehensive way.
{ | ||
// Destructor here does not run until exit from the block, | ||
// because value is assigned to. | ||
let $pat = $expr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this macro meant to just be $expr;
rather than let $pat = $expr;
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(At the very least, the comment seems to be incorrect?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is meant to be what it is. The point is that, depending on the pattern, temporaries in the rvalue may be promoted to either block or statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I think I commented on the incorrect one... the end_of_stmt
macro seems to have an identical comment, even though the behaviour is different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Wed, Jan 15, 2014 at 04:02:18PM -0800, Huon Wilson wrote:
(At the very least, the comment seems to be incorrect?)
Yes, that is true.
Ah, I forgot, I added some accessors to convert a |
* Generates the code to convert from a pointer (`~T`, `&T`, etc) | ||
* into an object (`~Trait`, `&Trait`, etc). This means creating a | ||
* pair where the first word is the pointer and the second word is | ||
* an appropriate vtable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it the other way around? i.e (vtable, pointer)
. The code itself is fine since it just uses abi::trt_field_{vtable, box}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. :)
I'm super excited about this! Heroic work, Niko. |
* Applied to an expression `expr` if `expr` -- or something | ||
* owned or partially owned by `expr` -- is going to be | ||
* indirectly referenced by a variable in a let statement. In | ||
* that case, the "temporary lifetime" or `expr` is extended |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "of", not "or"
Looks good from a first pass. I have just a few nits and comments. Thanks! |
… "quirky" parts of LLVM (see e.g. LLVM bug 9900) but also generating better code
@pcwalton I interpreted your comment as r+. I fixed the nits, as well as a minor-ish patch to handle 0-sized data structures in a better way, bypassing an LLVM crash I was encountering. If I misinterpreted your comment, no harm, as I expect it won't land on the first try anyhow. ;) |
While the issue is still open, I wonder whether the following observation is relevant. I used a freshly build rustc minutes ago from the source. // this compiles and works
fn xs_1() {
let xs = ['1','2','3','4','5','6','7','8','9','0'];
for i in xs.iter() { println!("{:c}", *i); }
}
// this works, too
fn xs_2() {
for i in "1234567890".chars() { println!("{:c}", i); }
}
// but this doesn't with compiler error
// error: borrowed value does not live long enough
fn xs_3() {
for i in ['1','2','3','4','5','6','7','8','9','0'].iter() {
println!("{:c}", *i);
}
} |
too. Previously I had omitted this case since function calls don't get the same treatment on the RHS, but it's different on the pattern and is more consistent -- the goal is to identify `let` statements where `ref` bindings create interior pointers.
@edwardw ah, thanks for pointing that out. This can be corrected by modifying the way we expand |
Based on the rules I wrote, I would expect the lifetime of the |
so that the "innermost enclosing statement" used for rvalue temporaries matches up with user expectations
@eddyb sigh. I hate that. I wonder if it is possible to have license text that omits the date, or maybe have a script to update the dates somehow based on git commit information :) |
… r=pcwalton Major changes: - Define temporary scopes in a syntax-based way that basically defaults to the innermost statement or conditional block, except for in a `let` initializer, where we default to the innermost block. Rules are documented in the code, but not in the manual (yet). See new test run-pass/cleanup-value-scopes.rs for examples. - Refactors Datum to better define cleanup roles. - Refactor cleanup scopes to not be tied to basic blocks, permitting us to have a very large number of scopes (one per AST node). - Introduce nascent documentation in trans/doc.rs covering datums and cleanup in a more comprehensive way. r? @pcwalton
There are two interesting kinds of breakage illustrated here: 1. `Box<Trait>` in many contexts is treated as `Box<Trait + 'static>`, due to [RFC 599]. However, in a type like `&'a Box<Trait>`, the `Box<Trait>` type will be expanded to `Box<Trait + 'a>`, again due to [RFC 599]. This, combined with the fix to Issue 25199, leads to a borrowck problem due the combination of this function signature (in src/libstd/net/parser.rs): ```rust fn read_or<T>(&mut self, parsers: &mut [Box<FnMut(&mut Parser) -> Option<T>>]) -> Option<T>; ``` with this call site (again in src/libstd/net/parser.rs): ```rust fn read_ip_addr(&mut self) -> Option<IpAddr> { let ipv4_addr = |p: &mut Parser| p.read_ipv4_addr().map(|v4| IpAddr::V4(v4)); let ipv6_addr = |p: &mut Parser| p.read_ipv6_addr().map(|v6| IpAddr::V6(v6)); self.read_or(&mut [Box::new(ipv4_addr), Box::new(ipv6_addr)]) } ``` yielding borrowck errors like: ``` parser.rs:265:27: 265:69 error: borrowed value does not live long enough parser.rs:265 self.read_or(&mut [Box::new(ipv4_addr), Box::new(ipv6_addr)]) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` (full log at: https://gist.github.com/pnkfelix/e2e80f1a71580f5d3103 ) The issue here is perhaps subtle: the `parsers` argument is inferred to be taking a slice of boxed objects with the implicit lifetime bound attached to the `self` parameter to `read_or`. Meanwhile, the fix to Issue 25199 (added in a forth-coming commit) is forcing us to assume that each boxed object may have a destructor that could refer to state of that lifetime, and *therefore* that inferred lifetime is required to outlive the boxed object itself. In this case, the relevant boxed object here is not going to make any such references; I believe it is just an artifact of how the expression was built that it is not assigned type: `Box<FnMut(&mut Parser) -> Option<T> + 'static>`. (i.e., mucking with the expression is probably one way to fix this problem). But the other way to fix it, adopted here, is to change the `read_or` method type to force make the (presumably-intended) `'static` bound explicit on the boxed `FnMut` object. (Note: this is still just the *first* example of breakage.) 2. In `macro_rules.rs`, the `TTMacroExpander` trait defines a method with signature: ```rust fn expand<'cx>(&self, cx: &'cx mut ExtCtxt, ...) -> Box<MacResult+'cx>; ``` taking a `&'cx mut ExtCtxt` as an argument and returning a `Box<MacResult'cx>`. The fix to Issue 25199 (added in aforementioned forth-coming commit) assumes that a value of type `Box<MacResult+'cx>` may, in its destructor, refer to a reference of lifetime `'cx`; thus the `'cx` lifetime is forced to outlive the returned value. Meanwhile, within `expand.rs`, the old code was doing: ```rust match expander.expand(fld.cx, ...).make_pat() { ... => immutable borrow of fld.cx ... } ``` The problem is that the `'cx` lifetime, inferred for the `expander.expand` call, has now been extended so that it has to outlive the temporary R-value returned by `expanded.expand`. But call is also reborrowing `fld.cx` *mutably*, which means that this reborrow must end before any immutable borrow of `fld.cx`; but there is one of those within the match body. (Note that the temporary R-values for the input expression to `match` all live as long as the whole `match` expression itself (see Issue rust-lang#3511 and PR rust-lang#11585). To address this, I moved the construction of the pat value into its own `let`-statement, so that the `Box<MacResult>` will only live for as long as the initializing expression for the `let`-statement, and thus allow the subsequent immutable borrow within the `match`. [RFC 599]: https://github.com/rust-lang/rfcs/blob/master/text/0599-default-object-bound.md
There are two interesting kinds of breakage illustrated here: 1. `Box<Trait>` in many contexts is treated as `Box<Trait + 'static>`, due to [RFC 599]. However, in a type like `&'a Box<Trait>`, the `Box<Trait>` type will be expanded to `Box<Trait + 'a>`, again due to [RFC 599]. This, combined with the fix to Issue 25199, leads to a borrowck problem due the combination of this function signature (in src/libstd/net/parser.rs): ```rust fn read_or<T>(&mut self, parsers: &mut [Box<FnMut(&mut Parser) -> Option<T>>]) -> Option<T>; ``` with this call site (again in src/libstd/net/parser.rs): ```rust fn read_ip_addr(&mut self) -> Option<IpAddr> { let ipv4_addr = |p: &mut Parser| p.read_ipv4_addr().map(|v4| IpAddr::V4(v4)); let ipv6_addr = |p: &mut Parser| p.read_ipv6_addr().map(|v6| IpAddr::V6(v6)); self.read_or(&mut [Box::new(ipv4_addr), Box::new(ipv6_addr)]) } ``` yielding borrowck errors like: ``` parser.rs:265:27: 265:69 error: borrowed value does not live long enough parser.rs:265 self.read_or(&mut [Box::new(ipv4_addr), Box::new(ipv6_addr)]) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` (full log at: https://gist.github.com/pnkfelix/e2e80f1a71580f5d3103 ) The issue here is perhaps subtle: the `parsers` argument is inferred to be taking a slice of boxed objects with the implicit lifetime bound attached to the `self` parameter to `read_or`. Meanwhile, the fix to Issue 25199 (added in a forth-coming commit) is forcing us to assume that each boxed object may have a destructor that could refer to state of that lifetime, and *therefore* that inferred lifetime is required to outlive the boxed object itself. In this case, the relevant boxed object here is not going to make any such references; I believe it is just an artifact of how the expression was built that it is not assigned type: `Box<FnMut(&mut Parser) -> Option<T> + 'static>`. (i.e., mucking with the expression is probably one way to fix this problem). But the other way to fix it, adopted here, is to change the `read_or` method type to force make the (presumably-intended) `'static` bound explicit on the boxed `FnMut` object. (Note: this is still just the *first* example of breakage.) 2. In `macro_rules.rs`, the `TTMacroExpander` trait defines a method with signature: ```rust fn expand<'cx>(&self, cx: &'cx mut ExtCtxt, ...) -> Box<MacResult+'cx>; ``` taking a `&'cx mut ExtCtxt` as an argument and returning a `Box<MacResult'cx>`. The fix to Issue 25199 (added in aforementioned forth-coming commit) assumes that a value of type `Box<MacResult+'cx>` may, in its destructor, refer to a reference of lifetime `'cx`; thus the `'cx` lifetime is forced to outlive the returned value. Meanwhile, within `expand.rs`, the old code was doing: ```rust match expander.expand(fld.cx, ...).make_pat() { ... => immutable borrow of fld.cx ... } ``` The problem is that the `'cx` lifetime, inferred for the `expander.expand` call, has now been extended so that it has to outlive the temporary R-value returned by `expanded.expand`. But call is also reborrowing `fld.cx` *mutably*, which means that this reborrow must end before any immutable borrow of `fld.cx`; but there is one of those within the match body. (Note that the temporary R-values for the input expression to `match` all live as long as the whole `match` expression itself (see Issue rust-lang#3511 and PR rust-lang#11585). To address this, I moved the construction of the pat value into its own `let`-statement, so that the `Box<MacResult>` will only live for as long as the initializing expression for the `let`-statement, and thus allow the subsequent immutable borrow within the `match`. [RFC 599]: https://github.com/rust-lang/rfcs/blob/master/text/0599-default-object-bound.md
There are two interesting kinds of breakage illustrated here: 1. `Box<Trait>` in many contexts is treated as `Box<Trait + 'static>`, due to [RFC 599]. However, in a type like `&'a Box<Trait>`, the `Box<Trait>` type will be expanded to `Box<Trait + 'a>`, again due to [RFC 599]. This, combined with the fix to Issue 25199, leads to a borrowck problem due the combination of this function signature (in src/libstd/net/parser.rs): ```rust fn read_or<T>(&mut self, parsers: &mut [Box<FnMut(&mut Parser) -> Option<T>>]) -> Option<T>; ``` with this call site (again in src/libstd/net/parser.rs): ```rust fn read_ip_addr(&mut self) -> Option<IpAddr> { let ipv4_addr = |p: &mut Parser| p.read_ipv4_addr().map(|v4| IpAddr::V4(v4)); let ipv6_addr = |p: &mut Parser| p.read_ipv6_addr().map(|v6| IpAddr::V6(v6)); self.read_or(&mut [Box::new(ipv4_addr), Box::new(ipv6_addr)]) } ``` yielding borrowck errors like: ``` parser.rs:265:27: 265:69 error: borrowed value does not live long enough parser.rs:265 self.read_or(&mut [Box::new(ipv4_addr), Box::new(ipv6_addr)]) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` (full log at: https://gist.github.com/pnkfelix/e2e80f1a71580f5d3103 ) The issue here is perhaps subtle: the `parsers` argument is inferred to be taking a slice of boxed objects with the implicit lifetime bound attached to the `self` parameter to `read_or`. Meanwhile, the fix to Issue 25199 (added in a forth-coming commit) is forcing us to assume that each boxed object may have a destructor that could refer to state of that lifetime, and *therefore* that inferred lifetime is required to outlive the boxed object itself. In this case, the relevant boxed object here is not going to make any such references; I believe it is just an artifact of how the expression was built that it is not assigned type: `Box<FnMut(&mut Parser) -> Option<T> + 'static>`. (i.e., mucking with the expression is probably one way to fix this problem). But the other way to fix it, adopted here, is to change the `read_or` method type to force make the (presumably-intended) `'static` bound explicit on the boxed `FnMut` object. (Note: this is still just the *first* example of breakage.) 2. In `macro_rules.rs`, the `TTMacroExpander` trait defines a method with signature: ```rust fn expand<'cx>(&self, cx: &'cx mut ExtCtxt, ...) -> Box<MacResult+'cx>; ``` taking a `&'cx mut ExtCtxt` as an argument and returning a `Box<MacResult'cx>`. The fix to Issue 25199 (added in aforementioned forth-coming commit) assumes that a value of type `Box<MacResult+'cx>` may, in its destructor, refer to a reference of lifetime `'cx`; thus the `'cx` lifetime is forced to outlive the returned value. Meanwhile, within `expand.rs`, the old code was doing: ```rust match expander.expand(fld.cx, ...).make_pat() { ... => immutable borrow of fld.cx ... } ``` The problem is that the `'cx` lifetime, inferred for the `expander.expand` call, has now been extended so that it has to outlive the temporary R-value returned by `expanded.expand`. But call is also reborrowing `fld.cx` *mutably*, which means that this reborrow must end before any immutable borrow of `fld.cx`; but there is one of those within the match body. (Note that the temporary R-values for the input expression to `match` all live as long as the whole `match` expression itself (see Issue rust-lang#3511 and PR rust-lang#11585). To address this, I moved the construction of the pat value into its own `let`-statement, so that the `Box<MacResult>` will only live for as long as the initializing expression for the `let`-statement, and thus allow the subsequent immutable borrow within the `match`. [RFC 599]: https://github.com/rust-lang/rfcs/blob/master/text/0599-default-object-bound.md
Major changes:
to the innermost statement or conditional block, except for in
a
let
initializer, where we default to the innermost block. Rulesare documented in the code, but not in the manual (yet).
See new test run-pass/cleanup-value-scopes.rs for examples.
us to have a very large number of scopes (one per AST node).
cleanup in a more comprehensive way.
r? @pcwalton