Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upnew scoping rules for safe dtors can yield spurious semi-colon or trailing unit expr #21114
Comments
pnkfelix
changed the title
new scoping rules for safe dtors often yield spurious semi-colon or trailing unit expr
new scoping rules for safe dtors can yield spurious semi-colon or trailing unit expr
Jan 13, 2015
kmcallister
added
A-lifetimes
A-destructors
labels
Jan 14, 2015
This comment has been minimized.
This comment has been minimized.
|
The solution here seems to be to adjust the infer lifetimes for temporaries generated from the I have a patch to do this on my branch, and so far it seems to work, but @nikomatsakis has pointed out to me that I may be better off waiting until after #20790 lands. |
This comment has been minimized.
This comment has been minimized.
|
(Nonetheless, there may be similar or related issues in other syntactic forms, such as |
pnkfelix
referenced
this issue
in pnkfelix/rust
Feb 2, 2015
This comment has been minimized.
This comment has been minimized.
|
(This was largely addressed by #21984. I am not sure whether there is much more we can actually do here. You can search for FIXME's in this commit bdb9f3e for most of the fallout that was caused by this in the end (its mostly with |
sfackler
referenced this issue
Feb 13, 2015
Closed
dropck seems overconservative when dealing with matches at the end of a block #22252
This comment has been minimized.
This comment has been minimized.
|
I think I'm having a similar issue: use std::collections::HashMap;
use std::sync::Mutex;
fn i_used_to_be_able_to(foo: &Mutex<HashMap<usize, usize>>) -> Vec<(usize, usize)> {
let mut foo = foo.lock().unwrap();
foo.drain().collect()
}
fn but_after_nightly_update_now_i_gotta(foo: &Mutex<HashMap<usize, usize>>) -> Vec<(usize, usize)> {
let mut foo = foo.lock().unwrap();
return foo.drain().collect();
}
fn main() {} |
This comment has been minimized.
This comment has been minimized.
felipesere
commented
Feb 13, 2015
|
I get the above mentioned error when running rustc
When using |
felipesere
added a commit
to felipesere/icepick
that referenced
this issue
Feb 13, 2015
This comment has been minimized.
This comment has been minimized.
|
@felipesere Your example, I think, is running afoul of the failure of the region system's model to precisely track the destruction order for r-value temporaries; see #22323. After rewriting your example to accommodate that short-coming, we have this: fn lines() -> Vec<String> {
let mut stdin = std::old_io::stdio::stdin();
let mut locked = stdin.lock();
locked.lines().map( |line| {
line.unwrap().trim().to_string()
}).collect()
}which compiles for me. So I do not think this is an instance of this ticket, but rather of #22323 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
drbawb
commented
Feb 15, 2015
|
I think I'm bumping up against this, I have a function that acquires a The code looks similar to this: type Context = Arc<RwLock<HashMap<Uuid, User>>>;
fn disconnect_user(ctx: Context, uid: Uuid) -> Result<String, String> {
// ....
match ctx.write().unwrap().remove(&uid) {
Some(user) => { Ok(format!("user removed")) },
None => { Err(format!("user not removed")) },
}
}Which yields this error on the latest nightly (
I seem to be able to work around it in two ways. I can either bind the result of the match exprsession to a variable and return that instead, or I can bind |
pnkfelix
self-assigned this
Feb 16, 2015
This comment has been minimized.
This comment has been minimized.
|
@drbawb yes, I suspect this is the same issue. I doubt I will be fixing this any time soon (i.e., I do not think it is a 1.0 blocker issue), so while I do hope to fix it eventually, I think you will need to live with the workarounds for now. |
eddyb
referenced this issue
May 9, 2016
Closed
Expressions in block tail supposed to outlive block variables dropped early. #33490
This comment has been minimized.
This comment has been minimized.
|
A full reproduction of @pnkfelix 's early example compiles today without the
That said, obviously this issue is about a bit more than that, and as he said:
@pnkfelix where does this stand today? |
This comment has been minimized.
This comment has been minimized.
|
@steveklabnik it seems like the cases of interest here got fixed via independent changes elsewhere in It might be interesting to track down which PR did this, but given that we have made a number of refinements to the region system since this was filed, I'm not astounded that it was fixed .... merely mildly surprised |
pnkfelix
closed this
May 25, 2016
This comment has been minimized.
This comment has been minimized.
|
This problem seems to still crop up with code like this, which doesn't compile unless use std::sync::Mutex;
fn main() {
let counter = Mutex::new(0);
if let Ok(_) = counter.lock() { }
} |
pnkfelix
reopened this
Sep 20, 2016
This comment has been minimized.
This comment has been minimized.
|
reopening based on discussion at #22252 |
jonas-schievink
referenced this issue
Oct 25, 2016
Closed
Borrow checker works wrongly with returned values. #37407
eddyb
referenced this issue
Apr 25, 2017
Closed
if let in block expression position borrow check mismatch #41495
This comment has been minimized.
This comment has been minimized.
|
(I will try to accumulate a representative set of currently failing examples in the description for ease of reference) |
Mark-Simulacrum
referenced this issue
Apr 30, 2017
Closed
Replacing a parameter declared as `mut a` with `let mut a = a;` causes program to no longer compile #31723
pnkfelix
referenced this issue
May 2, 2017
Closed
fallout from Sound Generic Drop to be addressed later #22321
pnkfelix
added
the
A-NLL
label
Sep 13, 2018
pnkfelix
added this to the Rust 2018 Release milestone
Sep 13, 2018
This was referenced Sep 20, 2018
pnkfelix
removed
the
A-NLL
label
Sep 25, 2018
pnkfelix
removed this from the Rust 2018 Release milestone
Sep 25, 2018
This comment has been minimized.
This comment has been minimized.
|
(Shifting effort into making a diagnostic to address this pain point; see #54556.) WIth that shift, this bug is dedicated to the hypothetical attempt to "fix" this problem in some way that isn't just changing diagnostics. But also with that shift, this bug is no longer part of the NLL work, nor on the Rust 2018 release milestone. |
pnkfelix
added a commit
to pnkfelix/rust
that referenced
this issue
Oct 3, 2018
This comment has been minimized.
This comment has been minimized.
|
Some months ago @Thiez wrote an interesting note that pointed out an oddity where a tail expression with I investigated, and discovered something interesting: Apparently the two forms do not have the same dynamic semantics, because the temporaries for the Here is a concrete demonstration illustrating this (play: #![feature(nll)]
struct T(&'static str, u8);
impl Drop for T {
fn drop(&mut self){ println!("Dropping T {:?}", self.0); }
}
fn ends_with_if_else() -> u8 {
println!("entered `ends_with_if_else`");
let _tmp = T("temp", 0);
if true { T("expr", 1).1 } else { T("expr", 2).1 }
}
fn ends_with_tail() -> u8 {
println!("entered `ends_with_tail`");
let _tmp = T("temp", 3);
T("expr", 4).1
}
fn main() {
{
let _outer1 = T("outer1", 5);
println!("invoke method `ends_with_if_else`");
ends_with_if_else();
println!("returned from `ends_with_if_else`");
}
println!("");
{
let _outer2 = T("outer2", 6);
println!("invoke method `ends_with_tail`");
ends_with_tail();
println!("returned from `ends_with_tail`");
}
}This prints:
The implication is that our current temporary r-value rules do not let temporaries from the tails of This discrepancy is perhaps unfortunate, but I don't know if it would make sense to try to change it. If we were to try to change it, we should probably attach such a change to a particular Rust edition shift (and we're too close to the release of the 2018 edition to attempt it there.) |
pnkfelix
added a commit
to pnkfelix/rust
that referenced
this issue
Oct 5, 2018
This comment has been minimized.
This comment has been minimized.
kwanyyoss
commented
Oct 7, 2018
I am confused. I thought the behavior of "ends_with_if_else" is the good one.
I added "let ret = ..." to the last statements and ended with a naked "ret" as returned value from both "ends_with_tail" and "ends_with_if_else". The behavior of the new forms looks completely correct to me: Playground
"expr" was dropped even before "result obtained" as it should be. I suppose this was also what @Thiez wanted. Why is |
This comment has been minimized.
This comment has been minimized.
|
@kwanyyoss wrote:
Because the former is assigning its result into a For better or for worse, the temporary r-value lifetime rules we have chosen dictate that the temporaries are dropped at different times for these two scenarios. There is much background discussion of this, as well as proposals for revising our semantics here. See e.g. |
pnkfelix
closed this
Oct 9, 2018
pnkfelix
reopened this
Oct 9, 2018
This comment has been minimized.
This comment has been minimized.
kwanyyoss
commented
Oct 16, 2018
|
Thanks @pnkfelix for the reply and references! I think the problem with this case is that the return value is just a "u8". As long as the type is "Copy", it is hard to see why the lifetime of any temporary where the final value came from needs to be extended. Along this line, I wonder if extending the lifetime of the entire tail expression may be too broad. Does it make sense to extended (the lifetime of) only the last object created by the tail expression rather than the whole expression? I can find descriptions of how C++ deals with return objects and temporaries using copy constructors. The way I understand it is that it is basically doing a "placement new" into an ancestor stack frame/scope. Is there a similar treatise of how Rust returns objects or bubbles up the value of a I think there is still a common belief that |
This comment has been minimized.
This comment has been minimized.
|
There's discussion about temporary lifetimes in the Reference but having an explicit section on this would make sense. Edit: Filed rust-lang-nursery/reference#452 |
This comment has been minimized.
This comment has been minimized.
|
@kwanyyoss wrote:
Part of the goal here is to have temporary r-value rules that are uniform, in the sense that they come directly from the syntactic structure from the program, and not from e.g. the types that have been inferred nor the results of the borrow-check analysis. Here's further elaboration of this point:
I don't know. My most immediate reaction to that is that it would really complicate encoding the cases where you need those intermediate values to survive long enough. (And of course, the interpretation of your question depends very much on how one interprets the phrase "the last object created" ...) |
This comment has been minimized.
This comment has been minimized.
|
I wrote up above:
As an attempt to illustrate this: people already complain about the fact that they are forced to add extra fn does_not_work() {
let x = &std::env::args().collect::<Vec<_>>().as_slice()[1..];
println!("{:?}", x);
}
fn works() {
println!("{:?}", &std::env::args().collect::<Vec<_>>().as_slice()[1..]);
}(See #15023 ) I suspect your proposal would further exacerbate this already present problem. But I have not done any experiment to validate that suspicion. |
pnkfelix
added
the
metabug
label
Nov 9, 2018
This comment has been minimized.
This comment has been minimized.
|
I actually think this issue as described is effectively resolved now, under NLL at least. There are related topics in terms of temporary lifetimes being unintuitive (#37612, #46413) and proposals to improve them (#15023). But this specific issue was about how the dropck rules caused a number of cases to appear to regress, and as far as I can tell, NLL fixes those instances. So I am going to tag this issue as NLL-fixed-by-NLL to reflect that. |
pnkfelix commentedJan 13, 2015
•
edited
Spawned off of #21022, #21972
There are a number of situations where a trailing expression like a for-loop ends up being treated by the region lifetime inferencer as requiring a much longer lifetime assignment than what you would intuitively expect.
A lot of the relevant cases have been addressed. In fact, its gotten to the point that I want to have a summary comment up here that specifies the status for each example. Each demo should link to a playpen (using AST-borrowck when that has a checkmark, and NLL when AST-borrowck does not have a checkmark). Some rows also include a link to the comment where it was pointed out.
# to get the nicest output. In most cases the 2018 output is the same, so I didn't fill in the column in those cases.More details for issue follow.
A simple example of this (from examples embedded in the docs for
io::stdio):Another example, this time from an example for
alloc::rc(where this time I took the time to add a comment explaining what I encountered):Luckily for #21022, the coincidence of factors here is not very frequent, which is why I'm not planning on blocking #21022 on resolving this, but instead leaving it as something to address after issues for the alpha have been addressed.
(I'm filing this bug before landing the PR so that I can annotation each of the corresponding cases with a FIXME so that I can go back and address them after I get a chance to investigate this properly.)