? vs `to_owned`

Hey Everyone,

Wanted to get some opinions on formatting. I have a bunch of functions that return Result<T, String>
and I just realized you can do Err("I am a string literal")?, since the ? does the type conversion implicitly. How do y'all think this compares to using
into/to_string/to_owned?

Have a nice day :slight_smile:

I often use Err(…)?; and feel like it's a concise/elegant way to report an error, including the implicit Into conversion.

1 Like

Something I'm probably alone in doing is return Err(_)?;, assuming there is an impl From<InnerErr> for OuterErr.

For the compiler it makes no difference (i.e. the return is never actually used), but it helps human readers while visually scanning for certain code, both because it's closer to the start of the line, and because it's longer and therefore more easily spotted.

This doesn't make the ? useless though, as it's more succinct in converting the error than .map_err(OuterErr::from) or some such.

3 Likes

It actually does sometimes!

This doesn't compile:

    let Some(x) = None::<u8> else {
        Err("uhoh")?;
    };

because https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c467f5b33e83edfbbd28e1317ba28920

error[E0308]: `else` clause of `let...else` does not diverge
 --> src/main.rs:2:35
  |
2 |       let Some(x) = None::<u8> else {
  |  ___________________________________^
3 | |         Err("uhoh")?;
4 | |     };
  | |_____^ expected `!`, found `()`
  |
  = note:   expected type `!`
          found unit type `()`
  = help: try adding a diverging expression, such as `return` or `panic!(..)`
  = help: ...or use `match` instead of `let...else`

But this does work:

    let Some(x) = None::<u8> else {
        return Err("uhoh")?;
    };

because there's a subtle difference between syntactically diverging and only value-/type-level diverging.

2 Likes

By the way, what also works is

    let Some(x) = None::<u8> else {
        Err("uhoh")?
    };

:slight_smile:

never type is fun, isn’t it?

4 Likes

Certainly, yes. But if I were to make a macro, say, that I wanted to use like return -- where rustfmt will add a ; -- then including the sortof-unnecessary return would probably be a good idea.

(I don't know this history behind rustfmt thinking that break; and continue; are better than the semicolon-less versions, but in a codebase under its thumb I'd be used to writing diverging things with a semicolon.)

1 Like

Of course, my point isn’ṯ to argue against that there’s a difference, but instead just to leave a hint for anyone who might actually run into an error like the one you posted, that there’s a shorter way to make it compile than adding a return.

Incidentally, speaking of rustfmt, the

    let Some(x) = None::<u8> else {
        Err("uhoh")?
    };

version get’s nicely compacted into a single line, too:

    let Some(x) = None::<u8> else { Err("uhoh")? };

which I’d consider an additional win ^^

Whereas, on the other hand, rustfmt does decide to format

    let Some(x) = None::<u8> else { return Err("uhoh")? };

back into

    let Some(x) = None::<u8> else {
        return Err("uhoh")?;
    };

adding more lines and more semicolons :-/


Once never type is stable (or using tricks) one could also write Err::<!, _>("uhoh")? which then even works with an additional semicolon.