diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index 5151bd2c6..6682893f6 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,66 +190,125 @@ 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. - -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`. - -```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. - -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`. +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 +> # 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")); +> # } +> ``` -```rust -fn try_option_some() -> Option { - let val = Some(1)?; - Some(val) -} -assert_eq!(try_option_some(), Some(1)); +> [!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(()) +> # } +> ``` -fn try_option_none() -> Option { - let val = None?; - Some(val) -} -assert_eq!(try_option_none(), None); -``` +> [!NOTE] +> The try propagation operator is sometimes called *the question mark operator*, *the `?` operator*, or *the try operator*. -r[expr.try.trait] -`?` cannot be overloaded. +r[expr.try.restricted-types] +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 @@ -883,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 @@ -913,6 +973,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