Comment by bestouff

5 years ago

Rust shines when doing error handling. No way to ignore errors, but properly handling them often adds just a single question mark to your code. Everything stays readable and lightweight.

Of course the error handling story is still perfectible but so far it's already one of the best I know.

The trouble I’ve had as a beginner is crafting error types for those “Union of multiple existing error types”. E.g., myfunc() can return an IO error or a Parse error. The boilerplate for creating a new error type (absent macros) is significant, and while I’m aware that there are macro crates available which automate this, it’s not clear to me which of these if any are “most standard” or otherwise how to distinguish between them.

You can "ignore" error is Rust using _ like in Go.

  • Not really. In Go you can `val, _ := func()` and use the value even if there is an error. AFAIK there is no equivalent in Rust (for Option) outside of unsafe shenaniganry. You can choose to panic / return err / etc, but you can't choose to use the value regardless of the presence of an error.

    • Yep. I'm pretty sure that even with unsafe shenanigans, you can't access the value without being very explicit about it. You'd need something like:

          let value = unsafe {
              match result {
                  Ok(value) => value,
                  _ => hint::unreachable_unchecked()
              }
          };
      

      At this point, the fact that you've skipped an error check should be abundantly clear to anyone reading your code.

    • You can return tuples from Rust fns just like you would in Go, if that's your thing - no unsafe necessary:

        fn foo() -> (usize, Result<(),()>) { (0, Ok(())) }
        let (a, err) = foo(); err?; // propigate error
        let (b, _) = foo(); // discard error
      

      Or more typically, you might use one of the many fn s such as unwrap_or, unwrap_or_else, unwrap_or_default, etc. - to provide your own appropriate default value. I usually find that useful default values are often caller specific anyways (and doesn't require remembering which fns return which default values on error):

        fn foo() -> Result<usize> { Ok(1) }
        fn bar() -> Option<usize> { None }
        let val = foo().unwrap_or(3); // val == 1
        let val = bar().unwrap_or(4); // val == 4
      

      Alternatively you can use out parameters, which occasionally crops up in Rust's own stdlib:

        let mut line = String::new();
        let _ = buf_read.read_line(&mut line);
        // ...use line, even if there was an error...
      

      Also, the "error" type might contain values itself, although you're certainly not ignoring the error if you use it:

        // https://doc.rust-lang.org/std/ffi/struct.OsString.html#method.into_string
        match os_string.into_string() {
            Ok(string) => println!("Valid UTF8 string: {}", string),
            Err(os_string) => println!("Invalid UTF8: {:?}", os_string),
        }

      1 reply →

    • If you're not going to use the success value, you can ignore errors in Rust easily:

          let _ = something_returning_Result();
      

      This does not even give a warning.

      1 reply →

  • That's still very explicit. If you don't bind the returned Result to something (`let _ = ...`), the compiler bitches at you.