Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
- [Closure expressions](expressions/closure-expr.md)
- [Loop expressions](expressions/loop-expr.md)
- [Range expressions](expressions/range-expr.md)
- [If and if let expressions](expressions/if-expr.md)
- [If expressions](expressions/if-expr.md)
- [Match expressions](expressions/match-expr.md)
- [Return expressions](expressions/return-expr.md)
- [Await expressions](expressions/await-expr.md)
Expand Down
1 change: 0 additions & 1 deletion src/attributes/type_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ let _ = EnumWithNonExhaustiveVariants::First as u8;

Non-exhaustive types are always considered inhabited in downstream crates.

[`if let`]: ../expressions/if-expr.md#if-let-expressions
[`match`]: ../expressions/match-expr.md
[attributes]: ../attributes.md
[enum]: ../items/enumerations.md
Expand Down
6 changes: 2 additions & 4 deletions src/const_eval.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ r[const-eval.const-expr.const-fn]
* Calls of [const functions] and const methods.

r[const-eval.const-expr.loop]
* [loop], [while] and [`while let`] expressions.
* [loop] and [while] expressions.

r[const-eval.const-expr.if-match]
* [if], [`if let`] and [match] expressions.
* [if] and [match] expressions.

r[const-eval.const-context]
## Const context
Expand Down Expand Up @@ -185,7 +185,6 @@ of whether you are building on a `64` bit or a `32` bit system.
[grouped]: expressions/grouped-expr.md
[interior mutability]: interior-mutability.md
[if]: expressions/if-expr.md#if-expressions
[`if let`]: expressions/if-expr.md#if-let-expressions
[lazy boolean]: expressions/operator-expr.md#lazy-boolean-operators
[let statements]: statements.md#let-statements
[literals]: expressions/literal-expr.md
Expand All @@ -202,4 +201,3 @@ of whether you are building on a `64` bit or a `32` bit system.
[struct]: expressions/struct-expr.md
[tuple expressions]: expressions/tuple-expr.md
[while]: expressions/loop-expr.md#predicate-loops
[`while let`]: expressions/loop-expr.md#predicate-pattern-loops
15 changes: 8 additions & 7 deletions src/destructors.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ leaves a drop scope all variables associated to that scope are dropped in
reverse order of declaration (for variables) or creation (for temporaries).

r[destructors.scope.desugaring]
Drop scopes are determined after replacing [`for`], [`if let`], and
[`while let`] expressions with the equivalent expressions using [`match`].
Drop scopes can be determined by replacing [`for`], [`if`], and [`while`]
expressions with equivalent expressions using [`match`], [`loop`] and
`break`.

r[destructors.scope.operators]
Overloaded operators are not distinguished from built-in operators and [binding
Expand Down Expand Up @@ -203,11 +204,11 @@ smallest scope that contains the expression and is one of the following:
* A statement.
* The body of an [`if`], [`while`] or [`loop`] expression.
* The `else` block of an `if` expression.
* The condition expression of an `if` or `while` expression, or a `match`
guard.
* The non-pattern matching condition expression of an `if` or `while` expression,
or a `match` guard.
* The body expression for a match arm.
* Each operand of a [lazy boolean expression].
* The pattern-matching condition and consequent body of [`if let`] ([destructors.scope.temporary.edition2024]).
* The pattern-matching condition(s) and consequent body of [`if`] ([destructors.scope.temporary.edition2024]).
* The entirety of the tail expression of a block ([destructors.scope.temporary.edition2024]).

> [!NOTE]
Expand Down Expand Up @@ -479,10 +480,10 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
[tuple indexing expression]: expressions/tuple-expr.md#tuple-indexing-expressions

[`for`]: expressions/loop-expr.md#iterator-loops
[`if let`]: expressions/if-expr.md#if-let-expressions
[`if let`]: expressions/if-expr.md#if-let-patterns
[`if`]: expressions/if-expr.md#if-expressions
[`let` statement]: statements.md#let-statements
[`loop`]: expressions/loop-expr.md#infinite-loops
[`match`]: expressions/match-expr.md
[`while let`]: expressions/loop-expr.md#predicate-pattern-loops
[`while let`]: expressions/loop-expr.md#while-let-patterns
[`while`]: expressions/loop-expr.md#predicate-loops
5 changes: 2 additions & 3 deletions src/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ ExpressionWithBlock ->
| UnsafeBlockExpression
| LoopExpression
| IfExpression
| IfLetExpression
| MatchExpression
)
```
Expand Down Expand Up @@ -311,9 +310,9 @@ They are never allowed before:

[`Copy`]: special-types-and-traits.md#copy
[`Drop`]: special-types-and-traits.md#drop
[`if let`]: expressions/if-expr.md#if-let-expressions
[`if let`]: expressions/if-expr.md#if-let-patterns
[`Sized`]: special-types-and-traits.md#sized
[`while let`]: expressions/loop-expr.md#predicate-pattern-loops
[`while let`]: expressions/loop-expr.md#while-let-patterns
[array expressions]: expressions/array-expr.md
[array indexing]: expressions/array-expr.md#array-and-slice-indexing-expressions
[assign]: expressions/operator-expr.md#assignment-expressions
Expand Down
3 changes: 1 addition & 2 deletions src/expressions/block-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ r[expr.block.attributes.inner-attributes]
[Inner attributes] are allowed directly after the opening brace of a block expression in the following situations:

* [Function] and [method] bodies.
* Loop bodies ([`loop`], [`while`], [`while let`], and [`for`]).
* Loop bodies ([`loop`], [`while`], and [`for`]).
* Block expressions used as a [statement].
* Block expressions as elements of [array expressions], [tuple expressions],
[call expressions], and tuple-style [struct] expressions.
Expand All @@ -282,7 +282,6 @@ fn is_unix_platform() -> bool {
[`for`]: loop-expr.md#iterator-loops
[`loop`]: loop-expr.md#infinite-loops
[`unsafe` blocks]: ../unsafe-keyword.md#unsafe-blocks-unsafe-
[`while let`]: loop-expr.md#predicate-pattern-loops
[`while`]: loop-expr.md#predicate-loops
[array expressions]: array-expr.md
[call expressions]: call-expr.md
Expand Down
190 changes: 102 additions & 88 deletions src/expressions/if-expr.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,55 @@
r[expr.if]
# `if` and `if let` expressions

## `if` expressions
# `if` expressions

r[expr.if.syntax]
```grammar,expressions
IfExpression ->
`if` Expression _except [StructExpression]_ BlockExpression
(`else` ( BlockExpression | IfExpression | IfLetExpression ) )?
`if` Conditions BlockExpression
(`else` ( BlockExpression | IfExpression ) )?

Conditions ->
Expression _except [StructExpression]_
| LetChain

LetChain -> LetChainCondition ( `&&` LetChainCondition )*

LetChainCondition ->
Expression _except [ExcludedConditions]_
| OuterAttribute* `let` Pattern `=` Scrutinee _except [ExcludedConditions]_

@root ExcludedConditions ->
StructExpression
| LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
```
<!-- TODO: The exception above isn't accurate, see https://github.com/rust-lang/reference/issues/569 -->
<!-- TODO: The struct exception above needs clarification, see https://github.com/rust-lang/reference/issues/1808
The chain grammar could use some work, see https://github.com/rust-lang/reference/issues/1811
-->

r[expr.if.intro]
An `if` expression is a conditional branch in program control.
The syntax of an `if` expression is a condition operand, followed by a consequent block, any number of `else if` conditions and blocks, and an optional trailing `else` block.
The syntax of an `if` expression is a sequence of one or more condition operands separated by `&&`,
followed by a consequent block, any number of `else if` conditions and blocks, and an optional trailing `else` block.

r[expr.if.condition-bool]
The condition operands must have the [boolean type].
r[expr.if.condition]
Condition operands must be either an [_Expression_] with a [boolean type] or a conditional `let` match.

r[expr.if.condition-true]
If a condition operand evaluates to `true`, the consequent block is executed and any subsequent `else if` or `else` block is skipped.
If all of the condition operands evaluate to `true` and all of the `let` patterns successfully match their [scrutinee]s,
the consequent block is executed and any subsequent `else if` or `else` block is skipped.

r[expr.if.else-if]
If a condition operand evaluates to `false`, the consequent block is skipped and any subsequent `else if` condition is evaluated.
If any condition operand evaluates to `false` or any `let` pattern does not match its scrutinee,
the consequent block is skipped and any subsequent `else if` condition is evaluated.

r[expr.if.else]
If all `if` and `else if` conditions evaluate to `false` then any `else` block is executed.

r[expr.if.result]
An if expression evaluates to the same value as the executed block, or `()` if no block is evaluated.
An `if` expression evaluates to the same value as the executed block, or `()` if no block is evaluated.

r[expr.if.type]
An `if` expression must have the same type in all situations.
Expand All @@ -43,6 +64,7 @@ if x == 4 {
println!("x is something else");
}

// `if` can be used as an expression.
let y = if 12 * 15 > 150 {
"Bigger"
} else {
Expand All @@ -52,39 +74,25 @@ assert_eq!(y, "Bigger");
```

r[expr.if.let]
## `if let` expressions

r[expr.if.let.syntax]
```grammar,expressions
IfLetExpression ->
`if` `let` Pattern `=` Scrutinee _except [LazyBooleanExpression]_ BlockExpression
(`else` ( BlockExpression | IfExpression | IfLetExpression ) )?
```
## `if let` patterns

r[expr.if.let.intro]
An `if let` expression is semantically similar to an `if` expression but in place of a condition operand it expects the keyword `let` followed by a pattern, an `=` and a [scrutinee] operand.

r[expr.if.let.pattern]
If the value of the scrutinee matches the pattern, the corresponding block will execute.
`let` patterns in an `if` condition allow binding new variables into scope when the pattern matches successfully.

r[expr.if.let.else]
Otherwise, flow proceeds to the following `else` block if it exists.

r[expr.if.let.result]
Like `if` expressions, `if let` expressions have a value determined by the block that is evaluated.
The following examples illustrate bindings using `let` patterns:

```rust
let dish = ("Ham", "Eggs");

// this body will be skipped because the pattern is refuted
// This body will be skipped because the pattern is refuted.
if let ("Bacon", b) = dish {
println!("Bacon is served with {}", b);
} else {
// This block is evaluated instead.
println!("No bacon will be served");
}

// this body will execute
// This body will execute.
if let ("Ham", b) = dish {
println!("Ham is served with {}", b);
}
Expand All @@ -94,47 +102,9 @@ if let _ = 5 {
}
```

r[expr.if.let.else-if]
`if` and `if let` expressions can be intermixed:

```rust
let x = Some(3);
let a = if let Some(1) = x {
1
} else if x == Some(2) {
2
} else if let Some(y) = x {
y
} else {
-1
};
assert_eq!(a, 3);
```

r[expr.if.let.desugaring]
An `if let` expression is equivalent to a [`match` expression] as follows:

<!-- ignore: expansion example -->
```rust,ignore
if let PATS = EXPR {
/* body */
} else {
/*else */
}
```

is equivalent to

<!-- ignore: expansion example -->
```rust,ignore
match EXPR {
PATS => { /* body */ },
_ => { /* else */ }, // () if there is no else
}
```

r[expr.if.let.or-pattern]
Multiple patterns may be specified with the `|` operator. This has the same semantics as with `|` in `match` expressions:
Multiple patterns may be specified with the `|` operator.
This has the same semantics as with `|` in [`match` expressions]:

```rust
enum E {
Expand All @@ -148,27 +118,71 @@ if let E::X(n) | E::Y(n) = v {
}
```

r[expr.if.let.lazy-bool]
The expression cannot be a [lazy boolean operator expression][expr.bool-logic].
Use of a lazy boolean operator is ambiguous with a planned feature change of the language (the implementation of if-let chains - see [eRFC 2947][_eRFCIfLetChain_]).
When lazy boolean operator expression is desired, this can be achieved by using parenthesis as below:
r[expr.if.chains]
## Chains of conditions

r[expr.if.chains.intro]
Multiple condition operands can be separated with `&&`.

<!-- ignore: pseudo code -->
```rust,ignore
// Before...
if let PAT = EXPR && EXPR { .. }
r[expr.if.chains.order]
Similar to a `&&` [_LazyBooleanOperatorExpression_], each operand is evaluated from left-to-right until an operand evaluates as `false` or a `let` match fails,
in which case the subsequent operands are not evaluated.

// After...
if let PAT = ( EXPR && EXPR ) { .. }
r[expr.if.chains.bindings]
The bindings of each pattern are put into scope to be available for the next condition operand and the consequent block.

// Before...
if let PAT = EXPR || EXPR { .. }
The following is an example of chaining multiple expressions, mixing `let` bindings and boolean expressions, and with expressions able to reference pattern bindings from previous expressions:

// After...
if let PAT = ( EXPR || EXPR ) { .. }
```rust
fn single() {
let outer_opt = Some(Some(1i32));

if let Some(inner_opt) = outer_opt
&& let Some(number) = inner_opt
&& number == 1
{
println!("Peek a boo");
}
}
```

[_eRFCIfLetChain_]: https://github.com/rust-lang/rfcs/blob/master/text/2497-if-let-chains.md#rollout-plan-and-transitioning-to-rust-2018
[`match` expression]: match-expr.md
The above is equivalent to the following without using chains of conditions:

```rust
fn nested() {
let outer_opt = Some(Some(1i32));

if let Some(inner_opt) = outer_opt {
if let Some(number) = inner_opt {
if number == 1 {
println!("Peek a boo");
}
}
}
}
```

r[expr.if.chains.or]
If any condition operand is a `let` pattern, then none of the condition operands can be a `||` [lazy boolean operator expression][_LazyBooleanOperatorExpression_] due to ambiguity and precedence with the `let` scrutinee.
If a `||` expression is needed, then parentheses can be used. For example:

```rust
# let foo = Some(123);
# let condition1 = true;
# let condition2 = false;
// Parentheses are required here.
if let Some(x) = foo && (condition1 || condition2) { /*...*/ }
```

r[expr.if.edition2024]
> [!EDITION-2024]
> Before the 2024 edition, let chains are not supported. That is, the [LetChain] grammar is not allowed in an `if` expression.

[_BlockExpression_]: block-expr.md
[_Expression_]: ../expressions.md
[_LazyBooleanOperatorExpression_]: operator-expr.md#lazy-boolean-operators
[_Pattern_]: ../patterns.md
[_Scrutinee_]: match-expr.md
[`match` expressions]: match-expr.md
[boolean type]: ../types/boolean.md
[scrutinee]: ../glossary.md#scrutinee
Loading