Tracking issue for rust-lang/rfcs#66: better temporary lifetimes.
Some unresolved questions to be settled when implementing:
Some examples where this matters:
fn identity<T>(x: T) -> T { x }
// Are these the same or different?
foo(&3);
foo::<&int>(&3);
My take: Probably we should just consider the fully inferred type.
cc me
I've got an example which seems related. Apparently as of 470dbef29 a let
binding is necessary before a call to .as_slice()
to keep it alive long enough to use in a function?
:+1:
Is there any reason we can't just rewrite:
foo().bar().baz(&bip().bop().boop())
as:
let mut tmp = foo();
let mut tmp = foo.bar();
{
let mut tmp2 = bip();
let mut tmp2 = tmp2.bop();
let mut tmp2 = tmp2.boop();
let mut tmp2 = &tmp2;
tmp.baz(tmp2)
}
And let the optimizer do it's thing? Given that rust's move semantics, I can't see how this could cause any problems. This is just a sanity check; I'd be happy with a "no, it's complicated".
This would fix the write!(io::stdout().lock(), "{}", thing);
case.
The thing is that destructors sometimes have side-effects (for
example, RefCell destructors). If we did the rewrite you suggested, at
least according to the current rules, it would mean that the
destrutors always execute at the end of the current block, more or
less, which is usually not what people want. The current rules are
mostly that temporary destructors end at the end of the current
statement, unless the temporary is being assigned into a let
-bound
variable, in which case they live as long as the variable. (This is
roughly what C++ does as well.) The goal of this RFC was to be smarter
about knowing when the temporary will be assigned into a let-bound
variable.
On Wed, Mar 02, 2016 at 09:22:19AM -0800, Steven Allen wrote:
Is there any reason we can't just rewrite:
foo().bar().baz(&bip().bop().boop())
as:
let mut tmp = foo(); let mut tmp = foo.bar(); let mut tmp2 = bip(); let mut tmp2 = tmp2.bop(); let mut tmp2 = tmp2.bop(); let mut tmp2 = tmp2.boop(); let mut tmp2 = &tmp2; tmp.baz(tmp2)
And let the optimizer do it's thing? Given that rust's move semantics, I can't see how this could cause any problems. This is just a sanity check; I'd be happy with a "no, it's complicated".
Reply to this email directly or view it on GitHub:
https://github.com/rust-lang/rust/issues/15023#issuecomment-191333403
The goal of this RFC was to be smarter about knowing when the temporary will be assigned into a let-bound variable.
Assuming you mean "when a temporary would be _referenced_ by a let-bound variable", I see why this is more complicated.
I just want to leave my 2c that this is a very frustrating issue for newbies. I didn't think of myself as a newbie, having written two fairly stable libraries in rust, but today I was trying to write a memory manager for micro-controllers, learning about unsafe
and PhantomData
and all sorts of fun stuff -- all of which went great. Then, when I was ready to test my library I hit this compiler error which I had never seen before.
Thinking it was something I did with the internals, I began to question my entire way of doing things. Long story short it took me the better part of 4 hours to finally realize that nothing was wrong with my library. No -- what was wrong is that I hadn't put a let
statement on every unwrap
. I really feel like rust has left me down -- the behavior as-is is super unintuative.
I wish I had listened to the compiler better... maybe that is the lesson I really should learn here.
@nikomatsakis What's the status of this issue?
@brson no change at all. But I've had it on my list of things to write up instructions for. In fact, I was just coming to take a stab at that.
OK, so, it's taking me a lot longer to prepare those instructions than I expected. In part this is because the original RFC is not very well specified. I've started a "amendment" (basically a rewrite) that both specifies the current behavior and tries to more precisely specify what RFC 66 should do. This will hopefully get done soon, you can see a draft here.
My basic plan (at a very high level) is to:
I'm wondering about how this feature will interact with non-lexical lifetimes. Consider the following:
fn main() {
let x;
{
x = &String::from("foo");
}
}
Currently, this results in an error: temporary value dropped here while still borrowed
. Under RFC 66, the lifetime of the temporary value (the result of String::from("foo")
) would be extended to encompass the lifetime of its referents. The original RFC is pretty unspecific about the exact behavior of this extension.
My question: with NLLs, the lifetime of the reference in x
is shortened:
fn main() {
let x;
{
x = &String::from("foo");
// `x` is dead here
// Is the `String` dropped here?
}
// Or is it dropped here?
}
Should the lifetime of the temporary created by String::from
be extended to match the lexical (scope-based) lifetime of x
, or would the compiler see that the reference stored in x
is dead and drop the String
immediately?
Just throwing my opinion at this one. It's been one of the more frustrating issues with getting myself acquainted with Rust. A lot of popular languages now actually encourage method chaining. C#'s LINQ being the prime example.
C#
var peopleOldEnough = people.Where(p => p.Age >= 18).Select(p => p.FirstName);
Not being able to call methods in a way that, at this point, feels natural when there's not a compelling reason why it needs to be that way only serves to act as a barrier between programmers and the language. I would love if this RFC got more attention. I bet this issue would be generating a lot of buzz if people knew
I understand why this isn't a high-priority issue. But, the beginner experience should be considered one of the most crucial things to nail down to drive Rust adoption. And, as a beginner, this is a bit of a roadblock.
Yes, I hit it few times per week and get upset when I need to write a sequence of let statements instead of a chain. Chain emphasizes that the final result is the most important thing in a statement. It reduces pressure on brain for a programmer / reader. A sequence of let identifiers makes every let defined variable equally important for a reader to follow, and it is only to finally realise that all except the last variable do not really matter. Clear productivity loss even for experienced programmers, in my opinion. Longer it is open, more loss is accumulated.
Does thinking about this as sets of constraints help?
It certainly would make Rust a lot more approachable if a variable lived for as long as it was needed. We could use tooling to make it clear where an expression's lifetime ends. While we loose a little on explicitness, we gain a lot on on readability and coding ergonomics.
@gilescope
It certainly would make Rust a lot more approachable if a variable lived for as long as it was needed.
Note that we are not proposing that — or at least, we are not proposing that we use lifetime inference to decide when a destructor runs. That would prevent us from making improvements like NLL without changing the runtime semantics of code, which is not good. This RFC is actually much more limited — it runs before any inference etc has been done.
I've not carved out any time for my expanded version of the RFC -- I should at least push it somewhere I guess -- but I still think this is a good idea =)
Forum discussion of cases where this is a semantic breaking change:
Most helpful comment
I just want to leave my 2c that this is a very frustrating issue for newbies. I didn't think of myself as a newbie, having written two fairly stable libraries in rust, but today I was trying to write a memory manager for micro-controllers, learning about
unsafe
andPhantomData
and all sorts of fun stuff -- all of which went great. Then, when I was ready to test my library I hit this compiler error which I had never seen before.Thinking it was something I did with the internals, I began to question my entire way of doing things. Long story short it took me the better part of 4 hours to finally realize that nothing was wrong with my library. No -- what was wrong is that I hadn't put a
let
statement on everyunwrap
. I really feel like rust has left me down -- the behavior as-is is super unintuative.I wish I had listened to the compiler better... maybe that is the lesson I really should learn here.