Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions dev-guide/src/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ When assigning rules to new paragraphs or modifying rule names, use the followin
- `intro`: The beginning paragraph of each section. It should explain the construct being defined overall.
- `syntax`: Syntax definitions or explanations when BNF syntax definitions are not used.
- `namespace`: For items only, specifies the namespace(s) the item introduces a name in. It may also be used elsewhere when defining a namespace (e.g., `r[attribute.diagnostic.namespace]`).
- `diverging`: The divergence behavior of an expression. Be sure to update the Divergence chapter, too.
6. When a rule doesn't fall under the above keywords, or for section rule IDs, name the subrule as follows:
- If the rule names a specific Rust language construct (e.g., an attribute, standard library type/function, or keyword-introduced concept), use the construct as named in the language, appropriately case-adjusted (but do not replace `_`s with `-`s).
- Other than Rust language concepts with `_`s in the name, use `-` characters to separate words within a "subrule".
Expand Down
125 changes: 124 additions & 1 deletion src/divergence.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,47 @@ fn example() {

See the following rules for specific expression divergence behavior:

- [asm.diverging.naked_asm] --- `naked_asm!`.
- [asm.diverging.noreturn] --- `noreturn` in `asm!`.
- [expr.arith-logic.diverging] --- Arithmetic and logic expressions.
- [expr.array.diverging] --- Array expressions.
- [expr.array.index.diverging] --- Index expressions.
- [expr.as.diverging] --- `as` expressions.
- [expr.assign.diverging] --- Assignment expressions.
- [expr.await.diverging] --- `.await` expressions.
- [expr.block.async.diverging] --- Async block expressions.
- [expr.block.diverging] --- Block expressions.
- [expr.bool-logic.diverging] --- Lazy boolean expressions.
- [expr.borrow.diverging] --- Borrow expressions.
- [expr.call.diverging] --- Call expressions.
- [expr.closure.diverging] --- Closure expressions.
- [expr.cmp.diverging] --- Comparison expressions.
- [expr.compound-assign.diverging] --- Compound assignment expressions.
- [expr.deref.diverging] --- Dereference expressions.
- [expr.field.diverging] --- Field expressions.
- [expr.if.diverging] --- `if` expressions.
- [expr.literal.diverging] --- Literal expressions.
- [expr.loop.block-labels.type] --- Labeled block expressions with `break`.
- [expr.loop.break-value.diverging] --- `loop` expressions with `break`.
- [expr.loop.break.diverging] --- `break` expressions.
- [expr.loop.continue.diverging] --- `continue` expressions.
- [expr.loop.for.diverging] --- `for` expressions.
- [expr.loop.infinite.diverging] --- Infinite `loop` expressions.
- [expr.match.diverging] --- `match` expressions.
- [expr.loop.while.diverging] --- `while` expressions.
- [expr.match.arms.diverging] and [expr.match.scrutinee.diverging] --- `match` expressions.
- [expr.match.empty] --- Empty `match` expressions.
- [expr.method.diverging] --- Method call expressions.
- [expr.negate.diverging] --- Negation expressions.
- [expr.paren.diverging] --- Parenthesized expressions.
- [expr.path.diverging] --- Path expressions.
- [expr.placeholder.diverging] --- Underscore expressions.
- [expr.range.diverging] --- Range expressions.
- [expr.return.diverging] --- `return` expressions.
- [expr.struct.diverging] --- Struct expressions.
- [expr.try.diverging] --- Try propagation expressions.
- [expr.tuple-index.diverging] --- Tuple indexing expressions.
- [expr.tuple.diverging] --- Tuple expressions.
- [statement.let.diverging] --- `let` statements.

> [!NOTE]
> The [`panic!`] macro and related panic-generating macros like [`unreachable!`] also have the type [`!`] and are diverging.
Expand Down Expand Up @@ -59,6 +90,90 @@ Any expression of type [`!`] is a diverging expression. However, diverging expre
> [!NOTE]
> Divergence can propagate to the surrounding block. See [expr.block.diverging].

r[divergence.place-read]
## Place expression reads for never types

r[divergence.place-read.intro]
When a [place expression] is used in a context that calls for a value, the place is *read*. However, certain situations take the place itself without reading its value. In these situations, when the place's type is the [never type], then the expression is not considered to diverge.

r[divergence.place-read.non-place]
A non-place expression always constitutes a read for divergence calculation.

r[divergence.place-read.not-read]
A place expression constitutes a read for divergence calculation **except** for the following situations:

- As the operand of a [borrow expression].
- As the scrutinee of a [`match` expression], when not every arm's pattern constitutes a read (see [divergence.place-read.patterns]).
- As the initializer of a [`let` condition], when the pattern does not constitute a read.
- As the initializer of a [`let` statement], when the pattern does not constitute a read.

> [!EXAMPLE]
> The following compile-fail examples are structured around [the behavior of block divergence][expr.block.diverging]. Each function returns `!`. Examples that contain a diverging place expression that is **not** read from cause the final type of the body to be `()`, which fails to type check. If the diverging place expression is read from, then every code path is diverging, and the type of the body would be `!` and it would compile successfully.
>
> ```rust,compile_fail,E0308
> fn borrow_operand_not_read(x: !) -> ! {
> &x;
> // ERROR: expected `!`, found `()`
> }
> ```
>
> ```rust,compile_fail,E0308
> fn match_scrutinee_not_read_with_non_reading_patterns(x: !) -> ! {
> match x {
> _ => {}
> };
> // ERROR: expected `!`, found `()`
> }
> ```
>
> In contrast, when an arm uses a binding pattern (which constitutes a read), the scrutinee `x: !` is read, and reading an uninhabited place diverges. Note that the match is a statement here, just like the non-reading example above. The difference is that this statement is now considered diverging, so the block has the never type because the block diverges and [does not have a final expression][expr.block.value-diverges-no-trailing-expr]:
>
> ```rust
> fn match_scrutinee_read_with_reading_patterns(x: !) -> ! {
> match x {
> a => {}
> }; // OK: this statement diverges
> }
> ```

r[divergence.place-read.patterns]
A pattern *constitutes a read* of the value it is matched against in all cases except as follows:

- The [wildcard pattern] `_` does not constitute a read.
- An [or-pattern] `p1 | p2 | ...` constitutes a read only if *all* of its alternatives constitute a read.

```rust
fn pattern_is_read(x: !) -> ! {
// OK: Pattern is read, so the match expression diverges.
match x {
a => {}
};
}
```

```rust,compile_fail,E0308
fn wildcard_pattern_not_read(x: !) -> ! {
match x {
_ => {}
};
// ERROR: expected `!`, found `()`
}
```

```rust
fn or_pattern_read_in_all_patterns(x: !) -> ! {
// OK: This statement is diverging.
let (a | a) = x;
}
```

```rust,compile_fail,E0308
fn or_pattern_not_read_if_any_alternative_is_non_reading(x: !) -> ! {
let (_ | _) = x;
// ERROR: expected `!`, found `()`
}
```

r[divergence.fallback]
## Fallback

Expand Down Expand Up @@ -89,3 +204,11 @@ If a type to be inferred is only unified with diverging expressions, then that t
<!-- TODO: This last point should likely should be moved to a more general "type inference" section discussing generalization + unification. -->

[`!`]: type.never
[borrow expression]: expr.operator.borrow
[`let` condition]: expr.if.let
[`let` statement]: statement.let
[`match` expression]: expr.match
[never type]: type.never
[or-pattern]: patterns.or
[place expression]: expr.place-value.place-memory-location
[wildcard pattern]: patterns.wildcard.intro
45 changes: 45 additions & 0 deletions src/expressions/array-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ const EMPTY: Vec<i32> = Vec::new();
[EMPTY; 2];
```

r[expr.array.diverging]
An array expression [diverges] if any of its operands diverges.

```rust
fn array_diverges(x: !) -> ! {
// OK, expression diverges.
[x];
}
```

r[expr.array.index]
## Array and slice indexing expressions

Expand Down Expand Up @@ -128,6 +138,39 @@ arr[10]; // warning: index out of bounds
r[expr.array.index.trait-impl]
The array index expression can be implemented for types other than arrays and slices by implementing the [Index] and [IndexMut] traits.

r[expr.array.index.diverging]
An index expression [diverges] if either of its operands diverges.

```rust
fn index_base_diverges(a: [i32; 1]) -> ! {
// OK, expression diverges.
({ loop {}; a })[0];
}

fn index_diverges(a: [i32; 1]) -> ! {
// OK, expression diverges.
a[{ loop {}; 0 }];
}
```

> [!NOTE]
> An index expression also diverges if the type of the indexed element is the [never type] and the value is [guaranteed to be read][divergence.place-read] (see [divergence.never]).
>
> ```rust
> fn diverging_place_read(x: [!; 1]) -> ! {
> // A read of a place expression produces a diverging block.
> let a = x[0];
> }
> ```
>
> ```rust,compile_fail,E0308
> fn diverging_place_no_read(x: [!; 1]) -> ! {
> // This does not constitute a read, and thus does not diverge.
> let _ = x[0];
> // ERROR: Expected type !, found ()
> }
> ```

[`Copy`]: ../special-types-and-traits.md#copy
[IndexMut]: std::ops::IndexMut
[Index]: std::ops::Index
Expand All @@ -136,9 +179,11 @@ The array index expression can be implemented for types other than arrays and sl
[const block expression]: expr.block.const
[constant expression]: ../const_eval.md#constant-expressions
[constant item]: ../items/constant-items.md
[diverges]: divergence
[inferred const]: items.generics.const.inferred
[literal]: ../tokens.md#literals
[memory location]: ../expressions.md#place-expressions-and-value-expressions
[never type]: type.never
[panic]: ../panic.md
[path]: path-expr.md
[slice]: ../types/slice.md
Expand Down
12 changes: 12 additions & 0 deletions src/expressions/await-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ r[expr.await.edition2018]
> [!EDITION-2018]
> Await expressions are only available beginning with Rust 2018.

r[expr.await.diverging]
An await expression [diverges] if the future's output type is the [never type].

```rust
async fn await_diverge(x: impl Future<Output = !>) -> ! {
// OK, expression diverges.
x.await;
}
```

r[expr.await.task]
## Task context

Expand Down Expand Up @@ -63,6 +73,8 @@ where the `yield` pseudo-code returns `Poll::Pending` and, when re-invoked, resu
[`poll::Pending`]: std::task::Poll::Pending
[`poll::Ready`]: std::task::Poll::Ready
[async context]: ../expressions/block-expr.md#async-context
[diverges]: divergence
[future]: std::future::Future
[never type]: type.never
[`IntoFuture`]: std::future::IntoFuture
[`IntoFuture::into_future`]: std::future::IntoFuture::into_future
24 changes: 23 additions & 1 deletion src/expressions/block-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn f() -> ! { loop {}; } // Diverges and has no final operand.
> As a control flow expression, if a block expression is the outer expression of an expression statement, the expected type is `()` unless it is followed immediately by a semicolon.

r[expr.block.diverging]
A block is considered to be [diverging][divergence] if all reachable control flow paths contain a diverging expression, unless that expression is a [place expression] that is not read from.
A block is considered to be [diverging][divergence] if all reachable control flow paths contain a diverging expression, unless that expression is a [place expression] that is [not read from][divergence.place-read].

```rust,no_run
fn no_control_flow() -> ! {
Expand Down Expand Up @@ -178,6 +178,27 @@ The actual data format for this type is unspecified.
> [!NOTE]
> The future type that rustc generates is roughly equivalent to an enum with one variant per `await` point, where each variant stores the data needed to resume from its corresponding point.

r[expr.block.async.diverging]
An async block expression does not [diverge].

```rust,compile_fail,E0308
async fn async_block_does_not_diverge() -> ! {
async { loop {} };
// ERROR: Expected type !, found ()
}
```

> [!NOTE]
> Evaluating the future such that the output type is the [never type] will result in a value whose type is the never type, and per [divergence.never] the expression diverges.
>
> ```rust
> async fn async_block_await_diverge() -> ! {
> // This explicitly specifies the type to be the never type because
> // otherwise the type inference would infer the output to be unit.
> let x: ! = async { loop {} }.await;
> }
> ```

r[expr.block.async.edition2018]
> [!EDITION-2018]
> Async blocks are only available beginning with Rust 2018.
Expand Down Expand Up @@ -339,6 +360,7 @@ fn is_unix_platform() -> bool {
[call expressions]: call-expr.md
[capture modes]: ../types/closure.md#capture-modes
[constant items]: ../items/constant-items.md
[diverge]: divergence
[diverges]: expr.block.diverging
[final operand]: expr.block.inner-attributes
[free item]: ../glossary.md#free-item
Expand Down
33 changes: 33 additions & 0 deletions src/expressions/call-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,37 @@ let three: i32 = add(1i32, 2i32);
let name: &'static str = (|| "Rust")();
```

r[expr.call.diverging]
A call expression [diverges] if any of its operands diverges.

```rust
fn callee_diverges(x: !) -> ! {
// OK, expression diverges.
{x}();
}

fn takes_argument(_: i32) {}

fn argument_diverges(x: !) -> ! {
// OK, expression diverges.
takes_argument(x);
}
```

> [!NOTE]
> If the return type of the called function is the [never type], then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges.
>
> ```rust
> fn exit() -> ! {
> loop {}
> }
>
> fn return_value_is_never() -> ! {
> // OK, expression diverges.
> exit();
> }
> ```

r[expr.call.desugar]
## Disambiguating function calls

Expand Down Expand Up @@ -103,5 +134,7 @@ Refer to [RFC 132] for further details and motivations.
[`default()`]: std::default::Default::default
[`size_of()`]: std::mem::size_of
[automatically dereferenced]: field-expr.md#automatic-dereferencing
[diverges]: divergence
[fully-qualified syntax]: ../paths.md#qualified-paths
[never type]: type.never
[non-function types]: ../types/function-item.md
22 changes: 22 additions & 0 deletions src/expressions/closure-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ A closure expression denotes a function that maps a list of parameters onto the
r[expr.closure.unique-type]
Each closure expression has a unique, anonymous type.

r[expr.closure.diverging]
A closure expression does not [diverge].

```rust,compile_fail,E0308
fn closure_does_not_diverge() -> ! {
|| -> ! { loop {} };
// ERROR: Expected type !, found ()
}
```

> [!NOTE]
> If the return type of the closure is the [never type], and the closure is called, then the resulting value will have the never type, and per [divergence.never] the resulting expression diverges.
>
> ```rust
> fn closure_call_diverge() -> ! {
> // OK, call expression diverges.
> || -> ! { loop {} }();
> }
> ```

r[expr.closure.captures]
Significantly, closure expressions _capture their environment_, which regular [function definitions] do not.

Expand Down Expand Up @@ -105,6 +125,8 @@ Attributes on closure parameters follow the same rules and restrictions as [regu
[block]: block-expr.md
[call traits and coercions]: ../types/closure.md#call-traits-and-coercions
[closure type]: ../types/closure.md
[diverge]: divergence
[function definitions]: ../items/functions.md
[never type]: type.never
[patterns]: ../patterns.md
[regular function parameters]: ../items/functions.md#attributes-on-function-parameters
Loading
Loading