The ?
operator is syntactic sugar which does get translated fairly early in the compilation (but not into actual source code text like a pre-processor would operate). As such, it is comparable to other things like how for
loops are syntactic sugar, too.
A for
loop is equivalent to a loop
expression containing a match
expression as follows:
'label: for PATTERN in iter_expr {
/* loop body */
}
is equivalent to
{
let result = match IntoIterator::into_iter(iter_expr) {
mut iter => 'label: loop {
let mut next;
match Iterator::next(&mut iter) {
Option::Some(val) => next = val,
Option::None => break,
};
let PATTERN = next;
let () = { /* loop body */ };
},
};
result
}
The desugaring of ?
is a bit vague, because its stable use cases are only for concrete types, while the actual desugaring uses a (still unstable) trait infrastructure within which, EXPR?
desugars to
match Try::branch(EXPR) {
ControlFlow::Continue(v) => v,
ControlFlow::Break(r) => return FromResidual::from_residual(r),
}
since this still unstable and thus an implementation detail, it might be more accurate to just give equivalent desugarings for the concrete (stable) types that are supported, so e.g. something like
match EXPR {
Ok(v) => v,
Err(e) => return Err(From::from(e)),
}
for Result
, or
match EXPR {
Some(v) => v,
None => return None,
}
for Option
.
Another thing that sets apart such syntactic sugar from preprocessors is that error messages can often handle the syntactic sugar in a nice, unexpanded manner. E.g. if you try something like
fn foo(x: Result<(), i32>) -> Result<(), u32> {
Ok(x?)
}
you get an error message that’s aware of the fact that a ?
operator was involved
error[E0277]: `?` couldn't convert the error to `u32`
--> src/lib.rs:2:9
|
1 | fn foo(x: Result<(), i32>) -> Result<(), u32> {
| --------------- expected `u32` because of this
2 | Ok(x?)
| ^ the trait `From<i32>` is not implemented for `u32`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following other types implement trait `From<T>`:
<f32 as From<i16>>
<f32 as From<i8>>
<f32 as From<u16>>
<f32 as From<u8>>
<f64 as From<f32>>
<f64 as From<i16>>
<f64 as From<i32>>
<f64 as From<i8>>
and 67 others
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, i32>>` for `Result<(), u32>`
as you can see in the lines
error[E0277]: `?` couldn't convert the error to `u32`
and
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait