From 66308efb1e0ef8ee4f6ab23f254515ef83f144d1 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 14 Jul 2025 15:57:43 -0700 Subject: [PATCH 1/3] Rename question mark operator This renames the "question mark operator" section to the "try propagation expression", and also drops the grammar name "error propagation expression". The "error propagation expression" isn't really accurate, since `?` isn't just for errors. The intent is to describe the expression by what it does (as most other expressions do) instead of how it looks. It also felt a little awkward to call it the "question mark expression". --- src/expressions/operator-expr.md | 12 ++++++++---- src/tokens.md | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index 5151bd2c6..b89ff2529 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -6,7 +6,7 @@ r[expr.operator.syntax] OperatorExpression -> BorrowExpression | DereferenceExpression - | ErrorPropagationExpression + | TryPropagationExpression | NegationExpression | ArithmeticOrLogicalExpression | ComparisonExpression @@ -190,15 +190,18 @@ assert_eq!(*y, 11); ``` r[expr.try] -## The question mark operator +## The try propagation expression r[expr.try.syntax] ```grammar,expressions -ErrorPropagationExpression -> Expression `?` +TryPropagationExpression -> Expression `?` ``` r[expr.try.intro] -The question mark operator (`?`) unwraps valid values or returns erroneous values, propagating them to the calling function. +The try propagation operator (`?`) unwraps valid values or returns erroneous values, propagating them to the calling function. + +> [!NOTE] +> The try propagation operator is sometimes called *the question mark operator*, *the `?` operator*, or *the try operator*. r[expr.try.restricted-types] It is a unary postfix operator that can only be applied to the types `Result` and `Option`. @@ -913,6 +916,7 @@ Like assignment expressions, compound assignment expressions always produce [the (function() { var fragments = { "#slice-dst-pointer-to-pointer-cast": "operator-expr.html#pointer-to-pointer-cast", + "#the-question-mark-operator": "operator-expr.html#the-try-propagation-expression", }; var target = fragments[window.location.hash]; if (target) { diff --git a/src/tokens.md b/src/tokens.md index 294753e69..8f8ae10df 100644 --- a/src/tokens.md +++ b/src/tokens.md @@ -907,7 +907,7 @@ usages and meanings are defined in the linked pages. | `<-` | LArrow | The left arrow symbol has been unused since before Rust 1.0, but it is still treated as a single token | `#` | Pound | [Attributes] | `$` | Dollar | [Macros] -| `?` | Question | [Question mark operator][question], [Questionably sized][sized], [Macro Kleene Matcher][macros] +| `?` | Question | [Try propagation expressions][question], [Questionably sized][sized], [Macro Kleene Matcher][macros] | `~` | Tilde | The tilde operator has been unused since before Rust 1.0, but its token may still be used r[lex.token.delim] @@ -1064,7 +1064,7 @@ r[lex.token.reserved-guards.edition2024] [paths]: paths.md [patterns]: patterns.md [placeholder lifetime]: lifetime-elision.md -[question]: expressions/operator-expr.md#the-question-mark-operator +[question]: expressions/operator-expr.md#the-try-propagation-expression [range]: expressions/range-expr.md [rangepat]: patterns.md#range-patterns [raw pointers]: types/pointer.md#raw-pointers-const-and-mut From 8334b19f39cbc1bb419240ec1f5c408b68c60239 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 14 Jul 2025 16:01:23 -0700 Subject: [PATCH 2/3] Move try examples to an example block This moves the examples to the intro. It also adds a ControlFlow example. The intent here is to prepare for changing the rest of the text, and the examples won't quite fit like the used to. I also prefer the idea of having an example near every intro section. Edited-by: TC --- src/expressions/operator-expr.md | 94 +++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index b89ff2529..35e3099b8 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -200,6 +200,73 @@ TryPropagationExpression -> Expression `?` r[expr.try.intro] The try propagation operator (`?`) unwraps valid values or returns erroneous values, propagating them to the calling function. +> [!EXAMPLE] +> ```rust +> # use std::num::ParseIntError; +> fn try_to_parse() -> Result { +> let x: i32 = "123".parse()?; // `x` is `123`. +> let y: i32 = "24a".parse()?; // Returns an `Err()` immediately. +> Ok(x + y) // Doesn't run. +> } +> +> let res = try_to_parse(); +> println!("{res:?}"); +> # assert!(res.is_err()) +> ``` +> +> ```rust +> fn try_option_some() -> Option { +> let val = Some(1)?; +> Some(val) +> } +> assert_eq!(try_option_some(), Some(1)); +> +> fn try_option_none() -> Option { +> let val = None?; +> Some(val) +> } +> assert_eq!(try_option_none(), None); +> ``` +> +> ```rust +> use std::ops::ControlFlow; +> +> pub struct TreeNode { +> value: T, +> left: Option>>, +> right: Option>>, +> } +> +> impl TreeNode { +> pub fn traverse_inorder(&self, f: &mut impl FnMut(&T) -> ControlFlow) -> ControlFlow { +> if let Some(left) = &self.left { +> left.traverse_inorder(f)?; +> } +> f(&self.value)?; +> if let Some(right) = &self.right { +> right.traverse_inorder(f)?; +> } +> ControlFlow::Continue(()) +> } +> } +> # +> # fn main() { +> # let n = TreeNode { +> # value: 1, +> # left: Some(Box::new(TreeNode{value: 2, left: None, right: None})), +> # right: None, +> # }; +> # let v = n.traverse_inorder(&mut |t| { +> # if *t == 2 { +> # ControlFlow::Break("found") +> # } else { +> # ControlFlow::Continue(()) +> # } +> # }); +> # assert_eq!(v, ControlFlow::Break("found")); +> # } +> ``` + > [!NOTE] > The try propagation operator is sometimes called *the question mark operator*, *the `?` operator*, or *the try operator*. @@ -215,19 +282,6 @@ If the value is `Err(e)`, then it will return `Err(From::from(e))` from the encl r[expr.try.result-ok] If applied to `Ok(x)`, then it will unwrap the value to evaluate to `x`. -```rust -# use std::num::ParseIntError; -fn try_to_parse() -> Result { - let x: i32 = "123".parse()?; // x = 123 - let y: i32 = "24a".parse()?; // returns an Err() immediately - Ok(x + y) // Doesn't run. -} - -let res = try_to_parse(); -println!("{:?}", res); -# assert!(res.is_err()) -``` - r[expr.try.behavior-std-option] When applied to values of the `Option` type, it propagates `None`s. @@ -237,20 +291,6 @@ If the value is `None`, then it will return `None`. r[expr.try.result-some] If applied to `Some(x)`, then it will unwrap the value to evaluate to `x`. -```rust -fn try_option_some() -> Option { - let val = Some(1)?; - Some(val) -} -assert_eq!(try_option_some(), Some(1)); - -fn try_option_none() -> Option { - let val = None?; - Some(val) -} -assert_eq!(try_option_none(), None); -``` - r[expr.try.trait] `?` cannot be overloaded. From e06cb25a4adfd1759090342c2daf3e5eb3dd02af Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 14 Jul 2025 16:10:35 -0700 Subject: [PATCH 3/3] Rewrite try to be written in terms of the `Try` trait This rewrites the section to say that it uses the `Try` trait. I felt it was a bit too awkward to avoid it (since it is unstable). This keeps an explicit list of the types that the `Try` trait is implemented for, and adds the missing types (like `ControlFlow`). The intent is that when `Try` is stabilized that the desuggaring note moves to a normative rule, the `expr.try.restricted-types` rule is removed (and instead direct the user to the standard library documentation). I'm still holding out hope that they change the terminology, since I think the current choice is going to be confusing. This has a hack to allow `feature(try_trait_v2)` by abusing the style checker by adding some spaces. I really wanted to keep this example tested to ensure that it is kept up-to-date. However, I may regret this in the future. Fixes https://github.com/rust-lang/reference/issues/1927 Edited-by: TC --- src/expressions/operator-expr.md | 63 ++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index 35e3099b8..6682893f6 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -198,7 +198,7 @@ TryPropagationExpression -> Expression `?` ``` r[expr.try.intro] -The try propagation operator (`?`) unwraps valid values or returns erroneous values, propagating them to the calling function. +The try propagation expression uses the value of the inner expression and the [`Try`] trait to decide whether to produce a value, and if so, what value to produce, or whether to return a value to the caller, and if so, what value to return. > [!EXAMPLE] > ```rust @@ -267,32 +267,48 @@ The try propagation operator (`?`) unwraps valid values or returns erroneous val > # } > ``` +> [!NOTE] +> The [`Try`] trait is currently unstable, and thus cannot be implemented for user types. +> +> The try propagation expression is currently roughly equivalent to: +> +> ```rust +> # #![ feature(try_trait_v2) ] +> # fn example() -> Result<(), ()> { +> # let expr = Ok(()); +> match core::ops::Try::branch(expr) { +> core::ops::ControlFlow::Continue(val) => val, +> core::ops::ControlFlow::Break(residual) => +> return core::ops::FromResidual::from_residual(residual), +> } +> # Ok(()) +> # } +> ``` + > [!NOTE] > The try propagation operator is sometimes called *the question mark operator*, *the `?` operator*, or *the try operator*. r[expr.try.restricted-types] -It is a unary postfix operator that can only be applied to the types `Result` and `Option`. - -r[expr.try.behavior-std-result] -When applied to values of the `Result` type, it propagates errors. - -r[expr.try.effects-err] -If the value is `Err(e)`, then it will return `Err(From::from(e))` from the enclosing function or closure. - -r[expr.try.result-ok] -If applied to `Ok(x)`, then it will unwrap the value to evaluate to `x`. - -r[expr.try.behavior-std-option] -When applied to values of the `Option` type, it propagates `None`s. - -r[expr.try.effects-none] -If the value is `None`, then it will return `None`. - -r[expr.try.result-some] -If applied to `Some(x)`, then it will unwrap the value to evaluate to `x`. - -r[expr.try.trait] -`?` cannot be overloaded. +The try propagation operator can be applied to expressions with the type of: + +- [`Result`] + - `Result::Ok(val)` evaluates to `val`. + - `Result::Err(e)` returns `Result::Err(From::from(e))`. +- [`Option`] + - `Option::Some(val)` evaluates to `val`. + - `Option::None` returns `Option::None`. +- [`ControlFlow`][core::ops::ControlFlow] + - `ControlFlow::Continue(c)` evaluates to `c`. + - `ControlFlow::Break(b)` returns `ControlFlow::Break(b)`. +- [`Poll>`][core::task::Poll] + - `Poll::Ready(Ok(val))` evaluates to `Poll::Ready(val)`. + - `Poll::Ready(Err(e))` returns `Poll::Ready(Err(From::from(e)))`. + - `Poll::Pending` evaluates to `Poll::Pending`. +- [`Poll>>`][`core::task::Poll`] + - `Poll::Ready(Some(Ok(val)))` evaluates to `Poll::Ready(Some(val))`. + - `Poll::Ready(Some(Err(e)))` returns `Poll::Ready(Some(Err(From::from(e))))`. + - `Poll::Ready(None)` evaluates to `Poll::Ready(None)`. + - `Poll::Pending` evaluates to `Poll::Pending`. r[expr.negate] ## Negation operators @@ -926,6 +942,7 @@ Like assignment expressions, compound assignment expressions always produce [the > Try not to write code that depends on the evaluation order of operands in compound assignment expressions. > See [this test] for an example of using this dependency. +[`Try`]: core::ops::Try [copies or moves]: ../expressions.md#moved-and-copied-types [dropping]: ../destructors.md [explicit discriminants]: ../items/enumerations.md#explicit-discriminants