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

Allow loops to return values other than () #352

Closed
wants to merge 8 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Loops returning values
ftxqxd committed Oct 4, 2014
commit 332a1edf58bcb2315fa1535c9bff69b90520e325
148 changes: 148 additions & 0 deletions active/0000-loops-returning-values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
Summary
=======

Extend `for`, `loop`, and `while` loops to allow them to return values other
than `()`:

- add an optional `else` clause that is evaluated if the loop ended without
using `break`;
- add an optional expression parameter to `break` expressions to break out of
a loop with a value.

Motivation
==========

Quite often a variable is used with loops to keep track of something. For
example, the following code could be used to find a value in a list:

```rust
fn find(list: Vec<int>, val: int) -> Option<uint> {
let mut index = None;
for (i, v) in list.iter().enumerate() {
if *v == val {
index = Some(i);
break;
}
}
index
}
```

However, this code relies on mutable state when it really shouldn’t be
necessary—what this code is actually doing is simply setting a variable to a
value *one time*, with a default value if the assignment statement was never
reached.

Loops also don’t fit in with Rust’s idea of ‘everything is an expression’: while
loops *are* expressions, their value is completely useless. Making loops return
meaningful values would fit in better with Rust’s idea of being able to use
everything as an expression. Iterator adaptors can often be used to similar
effect, but they aren’t always as flexible as `for`, `loop`, and `while` loops:
they cannot `return` from the enclosing function and do not provide any
guarantees to the compiler about how they run the given closure, preventing the
compiler from knowing that a variable will only be initialised once, for
example.

Detailed design
===============

Extend `for` and `while` (and `while let`) to have an optional extra `else`
clause. This clause is evaluated if and only if the loop finished iterating
without reaching a `break` statement. This `else` clause, when reached, makes
the loop expression evaluate to the value within. When omitted, it is equivalent
to an empty `else` clause returning `()`. The syntax for this additional clause
comes from Python, where it is used for the same thing.

Add an optional expression parameter to `break` statements following the label
(if any). A `break` expression with a value breaks out of the loop and makes the
loop evaluate to the given expression. The type of the `break` statement’s
expression must be the same as that of the `else` clause and that of any other
`break` expression. Because `loop` loops have no `else` clause, this restriction
does not apply to them.

An advantage of having this new kind of construct is that the compiler can know
that either the main loop body or the `else` clause will *always* be run at
least once. This means that the following code would be valid:

```rust
let haystack = vec![1i, 2, 3, 4];
let needle = 2;

let x;
frobnicate(for (i, v) in haystack.iter().enumerate() {
if v >= needle {
x = i;
break v;
}
} else {
x = -1;
0
});
```

because the compiler knows that `x` will be assigned to exactly once.

Example
-------

The following statement:

```rust
let x = while w {
code;
if cond { break brk; }
} else {
els
};
```

would iterate like a normal `while` loop does today. However, if `cond`
evaluates to `true`, then the entire loop would evaluate to `brk`, setting `x`
to `brk`. If `cond` never evaluated to `true` in its entire cycle (i.e., the
`break` statement was never reached), then the loop would evaluate to `els`,
thus setting `x` to `els`.

In other words, it would be roughly equivalent to something like this:

```rust
let x = {
let mut _res = None;
while w {
code;
if cond { _res = Some(brk); break; }
}
match _res {
None => {
els
}
Some(res) => res
}
};
```

Drawbacks
=========

* Complexity. This adds some complexity which perhaps could be considered
unnecessary. However, this does have precedent in languages like Python, and
so presumably does have some demand.
* The syntax is not very obvious: `else` perhaps suggests what would run if the
loop didn’t iterate over anything.

Alternatives
============

* Do nothing. Instead, rely on using mutable variables to keep track of
something within a loop.
* Use `nobreak` or something instead of `else`. This makes things a lot clearer,
but has the downside of introducing a new keyword, making this a
backward-incompatible change.
* Make iterators yield `Result<T, E>` instead of `Option<T>` when calling
`next`, and adjust `for` loops to evaluate to the expression parameter of any
`break` encountered or the `Err` part of `next`’s return value. This could be
done in addition to this proposal.

Unresolved questions
====================

None.