Skip to content
Closed
Changes from all commits
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
104 changes: 104 additions & 0 deletions proposals/p1871.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Introduce `var`/`let`-`else`-statement

<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->

[Pull request](https://github.com/carbon-language/carbon-lang/pull/1871)

<!-- toc -->

## Table of contents

- [Problem](#problem)
- [Background](#background)
- [Proposal](#proposal)
- [Details](#details)
- [Rationale](#rationale)
- [Alternatives considered](#alternatives-considered)
- [Unresolved questions](#unresolved-questions)

<!-- tocstop -->

## Problem

This statement is syntactic sugar for a set of other patterns, specifically it
guards a code block if a requirement of this code block is violated. That is,
there is actually no problem.
Comment on lines +27 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem, otherwise we should not pursue this proposal. I believe var/let-else solves a real problem, and it is important that this proposal articulate what that problem is. The problem has to do with making common programming tasks both readable and writable. The specific benefit of var/let-else is when the task has two cases:

  • One case which can be identified using a pattern, and the main body of the function following will be handling.
  • All other cases can be handled uniformly and briefly.

If code follows that pattern, then this construct has two advantages: it is concise and not going to introduce as much indenting as other constructs. Both of these help readability and writability. Consider:

match (my_container.LookUp(...)) {
  case .None => { return NotFound; }
  case .Some(result: String) => {
    very;
    long;
    block;
    of;
    code;
    return Found(processed_result);
  }
}

compared to:

let .Some(result: String) = my_container.LookUp(...)
    else { return NotFound; }
very;
long;
block;
of;
code;
return Found(processed_result);

The big question is whether this use case is important / common. And I think that comes down to looking at examples when you would use this construct. I think the "function returns an optional" situation is one common example where this comes up. Another example is when calling a function that returns an error that this function wants to transform into a different error. Other examples would be valuable to identify.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to search around for the "return early pattern" to find examples where this would be useful.


## Background

The idea goes back to the prior art in
[Swift](https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html#ID525)
to allow the execution of a statement depending on the Boolean value of an
expression. Swift used a **guard** statement to require that a condition must be
true in order for all following code in the current code block to be executed.
If the condition is not true, control needs to be transferred using a control
transfer statement as `break`, `continue`, `return`, or a function that does not
return.

[Rust](https://github.com/rust-lang/rfcs/blob/master/text/3137-let-else.md)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you instead add the actual pull so that our reviewers can feel the impression at that time?

followed suit and introduced the **let-else statement**:

```rust
let PATTERN: TYPE = EXPRESSION else DIVERGING_BLOCK;
```

as a more or less natural counterpart to its
[if-let expression](https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md).

This proposal adds this pattern as it seems to be rather popular and simplifies
common error-handling patterns.
Comment on lines +52 to +53
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to go deeper into this here.


## Proposal

A `let` or `var` declaration may be used with a
[refutable pattern](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/README.md#refutable-patterns)
if an `else` clause is supplied, as in:

```carbon
let (x: i32, true) = F(1) else {
// Can't use `x` here.
return false;
}
// `x` is an r-value
var (y: i32, true) = F(x) else {
return false;
}
// `y` is an l-value
y += 2;
```

If the pattern successfully matches the runtime value, values are bound
according to the pattern, and execution skips over the `else` code block and
proceeds to the next statement.

If the pattern does not match, the `else` code block will be executed. None of
the bindings from the pattern will be in scope in the `else` code block. No
control flow path in the `else` code block may continue to the statement after.
All paths must end with a `break`, `continue`, `return`, or call to a function
that never returns.

## Details

TBD after enough C in this RFC.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC - Request For Comment

Hence C is Comment(s). Like yours. ;)


## Rationale

This pattern is said to improve the readability of code, compared to an
equivalent `match` or `if` statement. It allows to keep code which handles
violated requirements next to the requirements.
Comment on lines +90 to +92
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rationales in Carbon are to be written in terms of Carbon's stated goals and principles. The proposal template has some links, the one that I think is relevant to this proposal is: [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)


## Alternatives considered

- `var`/`let` ... `or {...}`
- `var`/`let` ... `?: {...}` a short-circuited ternary operator
- `var`/`let` ... `otherwise {...}`
- introducer syntax: `guard var`/`let` ... `else {...}`
Comment on lines +96 to +99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These alternatives should include the advantages and disadvantages of these options and a rationale for why we chose the proposal over the alternative. If you don't know, that is a good time to reach out to the community.


## Unresolved questions

- How often is this pattern actually used?
- Is it really more readable?