Skip to content

Rename and rewrite the "question mark operator" #1931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 15, 2025
Merged
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
165 changes: 113 additions & 52 deletions src/expressions/operator-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ r[expr.operator.syntax]
OperatorExpression ->
BorrowExpression
| DereferenceExpression
| ErrorPropagationExpression
| TryPropagationExpression
| NegationExpression
| ArithmeticOrLogicalExpression
| ComparisonExpression
Expand Down Expand Up @@ -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<T, E>` and `Option<T>`.

r[expr.try.behavior-std-result]
When applied to values of the `Result<T, E>` 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<i32, ParseIntError> {
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<T>` 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<i32, ParseIntError> {
> 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<u8> {
> let val = Some(1)?;
> Some(val)
> }
> assert_eq!(try_option_some(), Some(1));
>
> fn try_option_none() -> Option<u8> {
> let val = None?;
> Some(val)
> }
> assert_eq!(try_option_none(), None);
> ```
>
> ```rust
> use std::ops::ControlFlow;
>
> pub struct TreeNode<T> {
> value: T,
> left: Option<Box<TreeNode<T>>>,
> right: Option<Box<TreeNode<T>>>,
> }
>
> impl<T> TreeNode<T> {
> pub fn traverse_inorder<B>(&self, f: &mut impl FnMut(&T) -> ControlFlow<B>) -> ControlFlow<B> {
> 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<u8> {
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<u8> {
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<T, E>`]
- `Result::Ok(val)` evaluates to `val`.
- `Result::Err(e)` returns `Result::Err(From::from(e))`.
- [`Option<T>`]
- `Option::Some(val)` evaluates to `val`.
- `Option::None` returns `Option::None`.
- [`ControlFlow<B, C>`][core::ops::ControlFlow]
- `ControlFlow::Continue(c)` evaluates to `c`.
- `ControlFlow::Break(b)` returns `ControlFlow::Break(b)`.
- [`Poll<Result<T, E>>`][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<Option<Result<T, E>>>`][`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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down