-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
loop-break-value (issue #961) #1624
Changes from 7 commits
a3316b9
7fda498
75b82f4
b7ca757
57a00c0
4500c23
ba6b0cc
3651781
5254944
894ae7c
d6e1594
32bdf90
e9495fb
b746484
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
- Feature Name: loop_break_value | ||
- Start Date: 2016-05-20 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
(This is a result of discussion of | ||
[issue #961](https://github.com/rust-lang/rfcs/issues/961) and related to RFCs | ||
[352](https://github.com/rust-lang/rfcs/pull/352) and | ||
[955](https://github.com/rust-lang/rfcs/pull/955).) | ||
|
||
Let a `loop { ... }` expression return a value via `break my_value;`. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
This pattern is currently hard to implement without resorting to a function or | ||
closure wrapping the loop: | ||
|
||
```rust | ||
fn f() { | ||
let outcome = loop { | ||
// get and process some input, e.g. from the user or from a list of | ||
// files | ||
let result = get_result(); | ||
|
||
if successful() { | ||
break result; | ||
} | ||
// otherwise keep trying | ||
}; | ||
|
||
use_the_result(outcome); | ||
} | ||
``` | ||
|
||
In some cases, one can simply move `use_the_result(outcome)` into the loop, but | ||
sometimes this is undesirable and sometimes impossible due to lifetimes. | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
This proposal does two things: let `break` take a value, and let `loop` have a | ||
result type other than `()`. | ||
|
||
### Break Syntax | ||
|
||
Four forms of `break` will be supported: | ||
|
||
1. `break;` | ||
2. `break 'label;` | ||
3. `break EXPR;` | ||
4. `break 'label EXPR;` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say that the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes just like today
|
||
|
||
where `'label` is the name of a loop and `EXPR` is an expression. | ||
|
||
### Result type of loop | ||
|
||
Currently the result-type of a 'loop' without 'break' is `!` (never returns), | ||
which may be coerced to any type), and the result type of a 'loop' with 'break' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consistency nit: "result-type" vs "result type" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/'/`/ I will not nit on any more of the formatting, but the issues with it probably should be fixed across the document. Namely, plain apostrophe (') does not begin/end an inline code snippet. |
||
is `()`. This is important since a loop may appear as | ||
the last expression of a function: | ||
|
||
```rust | ||
fn f() { | ||
loop { | ||
do_something(); | ||
// never breaks | ||
} | ||
} | ||
fn g() -> () { | ||
loop { | ||
do_something(); | ||
if Q() { break; } | ||
} | ||
} | ||
fn h() -> ! { | ||
loop { | ||
do_something(); | ||
// this loop is not allowed to break due to inferred `!` type | ||
} | ||
} | ||
``` | ||
|
||
This proposal changes the result type of 'loop' to `T`, where: | ||
|
||
* if a loop is "broken" via `break;` or `break 'label;`, the loop's result type must be `()` | ||
* if a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` | ||
* as a special case, if a loop is "broken" via `break EXPR;` or `break 'label EXPR;` where `EXPR` evaluates to type `!` (does not return), this does not place a constraint on the type of the loop | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see the need for this special case: there's no reason to write There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, and I suppose this is why it's not legal to type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, that's because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now (EDIT: Weird, this was a reply to an existing thread on the same line; explains why it has no the reply field though…) |
||
* if external constaint on the loop's result type exist (e.g. `let x: S = loop { ... };`), then `T` must be coercible to this type | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This list still has issues with handling of coercion. For example I imagine something like this ought to be valid:
where (Why this matters? Substitute There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the typing and coersions rules here can be exactly like those for match arms or if-else. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please write a diff against the RFC; I was not 100% sure how to handle coercions (same with the |
||
|
||
It is an error if these types do not agree or if the compiler's type deduction | ||
rules do not yield a concrete type. | ||
|
||
Examples of errors: | ||
|
||
```rust | ||
// error: loop type must be () and must be i32 | ||
let a: i32 = loop { break; }; | ||
// error: loop type must be i32 and must be &str | ||
let b: i32 = loop { break "I am not an integer."; }; | ||
// error: loop type must be Option<_> and must be &str | ||
let c = loop { | ||
if Q() { | ||
break "answer"; | ||
} else { | ||
break None; | ||
} | ||
}; | ||
fn z() -> ! { | ||
// function does not return | ||
// error: loop may break (same behaviour as before) | ||
loop { | ||
if Q() { break; } | ||
} | ||
} | ||
``` | ||
|
||
Examples involving `!`: | ||
|
||
```rust | ||
fn f() -> () { | ||
// ! coerces to () | ||
loop {} | ||
} | ||
fn g() -> u32 { | ||
// ! coerces to u32 | ||
loop {} | ||
} | ||
fn z() -> ! { | ||
loop { | ||
break panic!(); | ||
} | ||
} | ||
``` | ||
|
||
Example showing the equivalence of `break;` and `break ();`: | ||
|
||
```rust | ||
fn y() -> () { | ||
loop { | ||
if coin_flip() { | ||
break; | ||
} else { | ||
break (); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Result value | ||
|
||
A loop only yields a value if broken via some form of `break ...;` statement, | ||
in which case it yields the value resulting from the evaulation of the | ||
statement's expression (`EXPR` above), or `()` if there is no `EXPR` | ||
expression. | ||
|
||
Examples: | ||
|
||
```rust | ||
assert_eq!(loop { break; }, ()); | ||
assert_eq!(loop { break 5; }, 5); | ||
let x = 'a loop { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntactic nit: this should be |
||
'b loop { | ||
break 'a 1; | ||
} | ||
break 'a 2; | ||
}; | ||
assert_eq!(x, 1); | ||
``` | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
The proposal changes the syntax of `break` statements, requiring updates to | ||
parsers and possibly syntax highlighters. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
No alternatives to the design have been suggested. It has been suggested that | ||
the feature itself is unnecessary, and indeed much Rust code already exists | ||
without it, however the pattern solves some cases which are difficult to handle | ||
otherwise and allows more flexibility in code layout. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
It would be possible to allow `for`, `while` and `while let` expressions return | ||
values in a similar way; however, these expressions may also terminate | ||
"naturally" (not via break), and no consensus has been reached on how the | ||
result value should be determined in this case, or even the result type. | ||
It is thus proposed not to change these expressions at this time. | ||
|
||
It should be noted that `for`, `while` and `while let` can all be emulated via | ||
`loop`, so perhaps allowing the former to return values is less important. | ||
Alternatively, a new keyword such as `default` or `else` could be used to | ||
specify the other exit value as in: | ||
|
||
```rust | ||
fn first<T: Copy>(list: Iterator<T>) -> Option<T> { | ||
for x in list { | ||
break Some(x); | ||
} default { | ||
None | ||
} | ||
} | ||
``` | ||
|
||
The exact syntax is disputed. It is suggested that this RFC should not be | ||
blocked on this issue since break-with-value can still be implemented in the | ||
manner above after this RFC. See the | ||
[discussion of #961](https://github.com/rust-lang/rfcs/issues/961) | ||
for more on this topic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not that hard:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right. I was able to reduce my problem to this pattern:
Lifetimes make some of these problems horrible to think about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn. The problem with being British is you expect people to spot the sarcasm.
Option
then unwrap just because there isn't an appropriate default value is clumsy(noting that due to reference lifetimes it may be possible to give the value fed to
do_computation()
a name in the outer scope).Is that sufficient motivation?
The new lifetime rules under discussion may well simplify this problem, both with-and-without break-with-value, but I still think break-with-value is a neat pattern (or alternately, a generic break-from-block-with-value, but that opens another can of worms: the value if not broken out of).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, @sfackler 's example works fine without a mutable variable or an
Option
. Am I missing something?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, and maybe it's a good thing. Sounds like it's time I un-learned to never leave variables uninitialised (thanks to Rust's great flow & lifetime analysis).
Points 3 and 4 still hold (the example above can be four words shorter), but this weakens the argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point 3 - please give an example where these confusing-to-consider lifetimes are involved.
Point 4 - "a lot of extra code" when talking about essentially one extra assignment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current pattern is not necessarily hard, but the main problem I think is that the new pattern expresses much better what is going on.
It is immediately clear that the loop is meant to set the value, while with the code of @sfackler this is not te case. Especially when considering loops with more content or with multiple exit points.