Non-Lexical Lifetimes Based on Liveness in Rust
smallcultfollowing.com
smallcultfollowing.com
[1]: http://smallcultfollowing.com/babysteps/blog/2016/04/27/non-... [2]: https://news.ycombinator.com/item?id=11611436
This tradition will not be forsaken with non-lexical lifetimes.
let a = 1
let b = pointer to a
do_stuff(b)
do_other_stuff (a)
is legal but suddenly becomes illegal when you turn it into let a = 1
let b = pointer to a
do_stuff(b)
do_other_stuff (a)
// ... etc etc
do_more_stuff(b)
You add a legal statement down the function for a variable that's still valid and in scope, and the calls above it go kablooey. let a = 1
begin block scope {
let b = pointer to a
do_stuff(b)
} // end block scope and b is dead
do_other_stuff (a)
What the change would do is make that block scope exist, but it exists implicitly and invisibly. And it invisibly stretches over the call to do_other_stuff(a) by the insertion of the do_more_stuff(b) below it. And suddenly it breaks.Contrast:
let a = 1
begin block scope {
let b = pointer to a
do_stuff(b)
} // end block scope and b is dead
do_other_stuff (a)
// ... etc etc
do_more_stuff(b) // is obviously broken, b is not in scope
or let a = 1
begin block scope {
let b = pointer to a
do_stuff(b)
do_other_stuff (a) // is obviously broken, b is not dead
// ... etc etc
do_more_stuff(b)
} // end block scope way down hereI suppose it will mean a programmer might have to think a little harder if they want to manually preempt compilation errors, but fortunately the compiler is there to catch them if they don't get it right. The example the OP points out is a situation that will likely play out like the following in most circumstances:
- programmer adds new use of variable b.
- compiler points out an existing use of some other value a that could invalidate b, and, importantly, highlights all the moving parts (conflicting use of a, use of b that prolongs its lifetime, etc.) in the error message.
- programmer edits code to avoid the problem.
I don't think you should dock Rust for pointing out bad practices that other languages simply don't notice. I think you can dock it for complaining about code that's really OK, which just happens to be exactly what they're trying to fix here.
For instance, I'd bet that most C or C++ apps in production that use concurrency have some concurrency bug somewhere, that's so rare that nobody has hit it yet (or the effect of the bug is minor). Those apps are running in production and serving a valuable purpose. Rust would force you to solve the edge case before deploying the app in production.
Occasionally, it's the right set of engineering tradeoffs to deploy something that produces useful results 99.9% of the time and crashes .1% of the time. (But, I guess you can say, you can always use Rust's 'unsafe' keyword to acknowledge that something isn't quite right but you're deploying it anyway.)
Yes, but unfortunately that doesn't describe the situation we are usually presented with. The developers most likely have not calculated that it runs fine 99.9% of the time and has specific failure cases that account for 0.1% of the time, they just don't know about the error. That is vastly different than making an engineering tradeoff, since the people engineering it don't even know there's a tradeoff being made, and thus can't make an informed decision about it.
In some sense, rust actually enables the situation you are presenting, in that it requires more explicit reasoning about behavior, and if you want to wrap a chunk in unsafe after calculating that it should only negatively affect the program operation 0.1% of the time, well then you actually are making a tradeoff.
Fortunately, these sort of errors should be all function-local and are of course all flagged at compile time. Assuming Niko's usual focus on high-quality error messages continues, I would expect this sort of situation to be fairly clearly explained by the compiler.
With a minor addition I think it would remain very comprehensible: "you borrowed a here ^^^ ... gave a away here ^^^^, then used it's borrow again here ^^^^ after having given it away."
let slice = ...
capitalize(slice);
"unlet" slice // <- explicitly make it go out of scope