News for hackers. Stuff that matters.
Re:Non-Lexical Lifetimes in Rust by kibwen on Monday May 02, 2016 @10:17AM (#11611533)
Short background: in today's Rust, the lifetime of a reference is bound to a lexical scope. Sometimes this forces one to create temporary variables solely to change the lexical scope of a reference, which is an annoyance. Take the following example:

    let mut nums = Vec::new();
    nums.push(nums.len());
The above code would compile if we had non-lexical lifetimes, but today it doesn't. This is because method calls are effectively desugared to something like this:

    let mut nums = Vec::new();
    Vec::push(&mut nums, nums.len());
The arguments are evaluated left-to-right, and the mutable borrow prevents the immutable borrow later. Once non-lexical lifetimes arrives this will be possible thanks to control-flow analysis, but today we have to make a temporary:

    let mut nums = Vec::new();
    let length = nums.len();
    nums.push(length);
It doesn't involve any new user-facing features to the language and is fully backward-compatible, so it's basically just a straight quality-of-life improvement for Rust programmers.
Re:Non-Lexical Lifetimes in Rust by eridius on Monday May 02, 2016 @02:30PM (#11614107)
I'm not quite sure how non-lexical lifetimes is supposed to fix this issue. Control-flow analysis doesn't change the fact that the arguments are evaluated in left-to-right order, and it doesn't change the fact that the &mut reference is created prior to the method arguments being evaluated and lives until the method itself is invoked. Control flow analysis will simply confirm the fact that the &mut borrow exists while the arguments are evaluated.

What might fix this is a special-case rule where an implicit¹ & or &mut reference as a method receiver is temporarily ignored while the arguments are being evaluated (with the borrow checker double-checking after the arguments are evaluated that the & or &mut reference is still legal, i.e. that the referenced value wasn't moved and, in the case of a &mut reference, that no other borrow exists as a result of evaluating the arguments). This rule, being a special case, doesn't require non-lexical lifetimes.

¹I say implicit because it would be potentially confusing for an explicit reference to be ignored while the arguments are evaluated.

Re:Non-Lexical Lifetimes in Rust by kibwen on Monday May 02, 2016 @04:19PM (#11615196)
You're correct in that I was painting with too broad a stroke by attributing all of NLL to control-flow analysis, I was just trying to be brief. :) Having knowledge of control flow is just one of the approaches that will be used to achieve non-lexical lifetimes, though it is particularly applicable when also talking about MIR given that MIR is structured as a control-flow graph.
Re:Non-Lexical Lifetimes in Rust by cwzwarich on Monday May 02, 2016 @06:33PM (#11616286)
The assumed fix here is to distinguish between a mutable borrow and the reservation of a location to later do a mutable borrow. Locations based on local variables and their fields can be reserved, although a move out of the variable would invalidate a reservation. Other locations (e.g. the result of a function call) can't be reserved and would give an error.
Re:Non-Lexical Lifetimes in Rust by eridius on Monday May 02, 2016 @06:46PM (#11616362)
Seems like it would be confusing to allow

  let mut nums = Vec::new();
  nums.push(nums.len());
but disallow

  let mut nums = Box::new(Vec::new());
  nums.push(nums.len());
The second example would have to be disallowed because the mutable reference is created by calling DerefMut::deref_mut().
Re:Non-Lexical Lifetimes in Rust by cwzwarich on Monday May 02, 2016 @06:54PM (#11616403)
The particular case of Box is safe, and is along the lines of special knowledge about Box that is assumed by the type checker, but the special-cased nature of Box has been gradually decreasing over time. I don't think there would be an easy safe way to allow user-defined types to establish same guarantee.

In retrospect, changing the evaluation order of a function with respect to its arguments might have been a better choice, but something like the solution I mentioned is probably the only backwards-compatible option.

Re:Non-Lexical Lifetimes in Rust by webkike on Monday May 02, 2016 @04:09PM (#11615108)
My guess is that if a mutable reference dominates the lifetimes of all its borrows, then an exception to the rule can be made.
Re:Non-Lexical Lifetimes in Rust by megrimlock on Monday May 02, 2016 @04:31PM (#11615315)
That Niko's text-arrow lifetime annotations are so helpful here suggests a rust IDE could offer similar highlighting or annotations to explain borrow checker errors.

One of the ergonomic hurdles to rust adoption by experienced C++ users is that the language looks similar enough that such people may think they fully understand the semantics even before they've fully internalized all the details. It's a tricky zone when something feels familiar enough that you don't realize the remaining mistakes in your conceptualization.

Re:Non-Lexical Lifetimes in Rust by kibwen on Monday May 02, 2016 @05:02PM (#11615586)
At this very moment there is a PR from Niko that is about to land that will overhaul borrow checker error message reporting (and error formatting in general): https://github.com/rust-lang/rust/pull/32756 .
Re:Non-Lexical Lifetimes in Rust by Manishearth on Monday May 02, 2016 @05:00PM (#11615572)
The borrow checker already outputs these arrows.

This output is improving, too https://github.com/rust-lang/rust/pull/32756 -- feedback appreciated!

Re:Non-Lexical Lifetimes in Rust by untothebreach on Monday May 02, 2016 @11:34AM (#11612274)
My understanding was that implementing this is dependent on MIR fully landing, is that correct?
Re:Non-Lexical Lifetimes in Rust by steveklabnik on Monday May 02, 2016 @11:39AM (#11612322)
In an extremely strict sense, no, but in reality yes. MIR makes it so much easier to implement a feature like this that we will wait for it before adding this.
Re:Non-Lexical Lifetimes in Rust by TylerE on Monday May 02, 2016 @01:24PM (#11613469)
So is it likely that this will be a Rust 2.x kind of thing?
Re:Non-Lexical Lifetimes in Rust by GolDDranks on Monday May 02, 2016 @01:31PM (#11613534)
Not at all – 2.0 means a breaking change, but currently the borrow checker is just too restrictive. Loosening it a bit won't be a breaking change, as it allows strictly more code, not less. The landing of MIR is an implementation detail (a quite large at that, but nevertheless), and it shouldn't have any observable effects. (Except for maybe some bugs getting fixed.)
Re:Non-Lexical Lifetimes in Rust by steveklabnik on Monday May 02, 2016 @01:28PM (#11613502)
Not at all. MIR is not user-facing, it's a refactoring of compiler internals, and it's getting close to being finished. The reason Niko wrote this blog post was to start thinking about the eventual RFC to specify exactly how they should work.

There are no current plans for a Rust 2.0.

Re:Non-Lexical Lifetimes in Rust by kibwen on Monday May 02, 2016 @01:30PM (#11613524)
Nope, nowhere near that far off. MIR will be landing this year, and while we don't have a timeframe for NLL yet it's definitely a priority and it's clear that the devs are giving thought to it, as per this post.
Re:Non-Lexical Lifetimes in Rust by k__ on Monday May 02, 2016 @03:43PM (#11614851)
> it's basically just a straight quality-of-life improvement for Rust programmers

Nice to read. Are there any other such improvements planned?

Doing mostly scripting languages, Rust feels often a bit clunky with solutions like you mentioned or its unwrapping etc.

Re:Non-Lexical Lifetimes in Rust by kibwen on Monday May 02, 2016 @04:26PM (#11615259)
If you're aware of Rust's `try!()` macro for error handling, its functionality is being elevated into the language via the new `?` operator, which, in addition to being less verbose, will make for better error messages and also allow us to potentially extend the error propagation to types other than `Result` (such as `Option`). This feature is currently in nightly waiting to be stabilized, and the compiler codebase itself has been converted to use it: https://github.com/rust-lang/rust/pull/32390

There's also a huge overhaul to compiler error message formatting that should make error messages friendlier and more informative, and should hopefully be landing in nightly sometime today: https://github.com/rust-lang/rust/pull/32756 .

Also, part of the same work that is enabling non-lexical lifetimes (MIR, discussed here last week: https://news.ycombinator.com/item?id=11581681 ) is also going to enable incremental recompilation of Rust code, which should have a dramatic effect on turnaround time when developing code, as you'll spend enormously less time waiting for things to compile.

Re:Non-Lexical Lifetimes in Rust by msbarnett on Monday May 02, 2016 @04:56PM (#11615531)
try!/? doesn't seem to really do much to solve the clunky ergonomics around Result/Optionals because, while shorter, it also creates an enormous amount of "upward pressure" for Results: to use try!/?, your method has to return an Result, which puts pressure on the caller to use try!/?, which then has to be defined to return an Result, which then puts pressure on...etc

So now instead of dealing with errors or none "at the boundaries", the codebase is shot-through with uncertainty just to get access to a less-clunky syntax.

It also doesn't solve silly things like io::stdout().flush().unwrap(), where you have to unwrap an empty return value, because there's nothing sane to do if it's not Ok(()) other than panic. But the only thing that would really solves those ergonomics is proper exceptions.

edit: And if every function is just going to be defined as returning Result<foo> instead of foo it feels like the language might as well bite the bullet and add a fn definition syntax that sugars the endless repetition of -> Result<foo> just for readability's sake.

Re:Non-Lexical Lifetimes in Rust by kibwen on Monday May 02, 2016 @05:16PM (#11615701)

  > So now instead of dealing with Nones "at the 
  > boundaries", the codebase is shot-through with 
  > uncertainty just to get access to a less-clunky syntax.
I'm not sure what this is referring to. The try macro and ? operator are solely for propagation, it doesn't make sense to speak of someone restructuring their code "just to get access" to them. Either you were propagating an error before, or you weren't. And if you weren't propagating it that means that you were handling it, which, if you're capable of doing so, is certainly better than propagating.

As for exceptions, having them is a non-starter given that Rust intends to be useful in environment where unwinding is simply unavailable. Both Dropbox and Firefox make use of Rust code compiled without any unwinding support. So you can either bifurcate your ecosystem with factions that alternately support errors-as-exceptions and errors-as-return-values, or you can rally around a single one and work to make its usage as nice as you can.

Re:Non-Lexical Lifetimes in Rust by msbarnett on Monday May 02, 2016 @05:44PM (#11615941)
> I'm not sure what this is referring to. The try macro and ? operator are solely for propagation, it doesn't make sense to speak of someone restructuring their code "just to get access" to them. Either you were propagating an error before, or you weren't. And if you weren't propagating it that means that you were handling it, which, if you're capable of doing so, is certainly better than propagating.

It's about ergonomics. The concern is what happens when a junior dev hits some Result<> they don't really know how to deal with. Something like this:

    io::stdout().flush().unwrap()
This kind of sucks. The "verb" of this line of code is flush(), and in most languages the verb of a line of code is the last thing on it. It's what people are used to scanning for. .unwrap()s bury it in the middle of the line, which makes code harder to scan for verbs. Additionally, everybody has "unwrap is a code smell" chanted at them by Rust people "in the know".

So I want to make this go away, but there isn't a sensible thing for me to do if the universe is fucked to the point where flush isn't working, so matching on the result feels like enormous overkill because I don't really know what to do with the error.

    io::stdout().flush()?
Reads a hell of a lot better. It communicates what I'm actually trying to do more simply, and scans a lot better. Of course, it only works in functions that return Result<>s, because it's a Result propagation mechanism, not a do-or-die mechanism.

So if I'm a junior programmer, coming from some less strict language, a bit confused about all of this and I want code to read the way I expect it to read coming from other languages, I might start sprinkling in ?s every time I have to deal with these "Result" thingys. Of course, then the compiler yells at me because my caller has to unwrap Results, so I sprinkle in some more ?s and change my return type, and then...etc.

Pretty soon every function in the codebase returns a Result<> just so the code stays more readable and they can punt errors to main and exit, like they're used to.

tl;dr I think we're likely to see junior devs using a lot more Result<>s than they need to, because ? is more readable and "don't use unwrap" is Dogma.

Re:Non-Lexical Lifetimes in Rust by comex on Monday May 02, 2016 @11:11PM (#11617589)
For what it's worth, on Unix writing to stdout (including flushing a buffer) can fail in an extremely common situation: your process was piped to another, which exited before you called flush. (This also causes you to receive SIGPIPE, but Rust blocks SIGPIPE by default.) For example, `head` exits after the requested amount of data was read, and `less` exits immediately when the user presses 'q'. Since neither of those is an error, if you want to be well behaved, you should probably either ignore write errors or (preferably[1]) check for EPIPE in particular and respond by silently exiting - not panicking, which writes to stderr.

Not sure how well this generalizes to your broader complaint. One could argue that in general, you really do need to think about errors to write good software, so even if languages with exceptions make it slightly easier to write of code that doesn't, that's actually an antifeature because it encourages sloppiness. But maybe that's overly punitive rather than pragmatic. Still, I don't think the answer is exceptions.

[1] preferably because of course other errors are possible, especially if stdout is redirected to a file on disk - but then, in a quick and dirty program, if you ignore errors, you're doing no worse than umpteen million C programs that call printf without checking the return value.

Re:Non-Lexical Lifetimes in Rust by msbarnett on Tuesday May 03, 2016 @12:08AM (#11617843)
Solid point, and flushing a buffer isn't necessarily the best example, other than I think it's an obvious place where a less-than-experienced dev is forced to deal with a Result they don't know what to do with and might turn to a beguilingly simple operator that seems to make the problem go away but encourages them to just keep punting Results up the call stack.

But you could make the case that I'm being overly pessimistic in focusing on the worst case mess that could be made with this feature.

Re:Non-Lexical Lifetimes in Rust by kibwen on Monday May 02, 2016 @07:02PM (#11616448)
"Don't use unwrap" is only dogma in library code, because ultimately it should be up to the ultimate library consumer (the application) as to whether an erroneous situation should cause the program to exit. Relative to this, unwrapping is perfectly acceptable in application code.
Re:Non-Lexical Lifetimes in Rust by msbarnett on Monday May 02, 2016 @07:07PM (#11616490)
That's a message I'm not sure is getting through to end-users.

Eg) plenty of people posting code snippets asking for help in the usual places (/r/rust, the forum, SO) will immediately have 5 commentors jump down their throat if there's an unwrap in sight.

Re:Non-Lexical Lifetimes in Rust by rkangel on Wednesday May 04, 2016 @08:37AM (#11627868)
> Relative to this, unwrapping is perfectly acceptable in application code.

Unwrapping may be acceptable in application code - it depends on your application. Specifically it depends on whether you need specific handling of unexpected events.

If your program is a 'script' - by which I mean something that executes for a short period of time to perform a series of tasks and then exits - then panicking on an error is a reasonable way of aborting.

On the other hand, if it's a daemon, then you will likely want to gracefully handle all error conditions and carry on.

I think the issue here is that Rust is primarily used as a systems programming language and usage tends more to fall in the 'daemon' category. In that mentality you handle every error (even if it's just to print something), and that is probably where the general community pointers towards avoiding unwrap come from.

Re:Non-Lexical Lifetimes in Rust by viraptor on Monday May 02, 2016 @07:09PM (#11616512)
> like io::stdout().flush().unwrap(), where you have to unwrap an empty return value, because there's nothing sane to do if it's not Ok(()) other than panic

Try to flush other files, finish off transactions, remove potentially hanging locks, alert on stderr or in logs, etc. I don't think there isn't anything sane to do. Every app should have some global "something's fucked, try to clean up as much as possible" code path.

Re:Non-Lexical Lifetimes in Rust by steveklabnik on Monday May 02, 2016 @04:22PM (#11615225)
One thing that would be helpful for those looking to write scripts is to use crates which provide a higher level API. So for example, the text-io crate wraps up the lower-level standard library interface to make it easier to use, though it gives you much less control over errors.

Due to Rust's low-level nature, we've focused on the low-level stuff first: the higher-level APIs can then be built on top. I think this kind of thing would be more helpful to you than actual language improvements in many cases.

Re:Non-Lexical Lifetimes in Rust by k__ on Monday May 02, 2016 @05:00PM (#11615575)
Yes, I understand that. I just had the hope, that the low level stuff is done with 1.0