Skip to content
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

Better temporary lifetimes (tracking issue for RFC 66) #15023

Open
nikomatsakis opened this issue Jun 19, 2014 · 15 comments

Comments

Projects
None yet
@nikomatsakis
Copy link
Contributor

commented Jun 19, 2014

Tracking issue for rust-lang/rfcs#66: better temporary lifetimes.

Some unresolved questions to be settled when implementing:

  1. This implies that the lifetimes of temporaries is not known until after typeck. I think this is ok but it is a phase change which can sometimes be tricky (currently temporary lifetimes are known before typeck).
  2. We have to specify the precise rules over when a temporary is extended. There are various subtle cases to be considered:
  • Clearly we must consider whether the parameter type is a reference with a lifetime that also appears in the return type. Does the variance in the return type matter? (I think: no, too subtle and not worth it.)
  • When do we decide what the type of the parameter is? Do we consider the declared type, the type after inference, or a hybrid?

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.

@jdm

This comment was marked as off-topic.

Copy link
Contributor

commented Jul 22, 2014

cc me

@killerswan

This comment has been minimized.

Copy link
Contributor

commented Jul 26, 2014

I've got an example which seems related. Apparently as of 470dbef a let binding is necessary before a call to .as_slice() to keep it alive long enough to use in a function?

@schickling

This comment was marked as off-topic.

Copy link

commented Dec 6, 2014

👍

@mbrubeck mbrubeck changed the title Better temporary lifetimes (tracking issue for RFC 31) Better temporary lifetimes (tracking issue for RFC 66) Nov 6, 2015

@Stebalien

This comment has been minimized.

Copy link
Contributor

commented Mar 2, 2016

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.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Mar 2, 2016

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:
#15023 (comment)

@Stebalien

This comment has been minimized.

Copy link
Contributor

commented Mar 3, 2016

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.

@vitiral

This comment has been minimized.

Copy link
Contributor

commented Aug 28, 2016

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.

@brson

This comment has been minimized.

Copy link
Contributor

commented Mar 1, 2017

@nikomatsakis What's the status of this issue?

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Mar 2, 2017

@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.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented Mar 2, 2017

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:

  • step 1: convert the construction of the "extended temporary" tables into on-demand
    • I hope that typeck doesn't need these results; I think they are only needed by borrowck, but I have to verify
  • step 2: have that construction also demand the signatures of functions that are called and the typeck tables, so that we can find out what methods are being called
    • if that doesn't work, a bit more refactoring will be needed
    • if it does work, we should be able to use that to compute the temporary tables easily enough...

@chriskrycho chriskrycho referenced this issue Apr 1, 2017

Closed

Document all features #9

18 of 48 tasks complete
@cramertj

This comment has been minimized.

Copy link
Member

commented Aug 7, 2017

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?

@natepisarski

This comment has been minimized.

Copy link

commented Dec 11, 2017

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.

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

  • It's probably 100% possible to implement today
  • There is an existing backwards-compatible RFC for this feature

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.

@avkonst

This comment has been minimized.

Copy link

commented Dec 13, 2017

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.

@gilescope

This comment has been minimized.

Copy link
Contributor

commented May 15, 2018

Does thinking about this as sets of constraints help?

http://smallcultfollowing.com/babysteps/blog/2018/04/27/an-alias-based-formulation-of-the-borrow-checker/

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.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

commented May 15, 2018

@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 =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.