Hacker News new | ask | show | jobs
by ryandrake 641 days ago
I'm just now learning Rust, as a long time C++'er, and this was the first part of my Rust journey where I thought to myself, "Boy, this really smells--this couldn't possibly be the idiomatic Rust Way to handle functions that can produce different types of errors. I must be doing something wrong!"

For example, I have a function that takes an array of bytes, decodes it as UTF-8 to text, parses that text into an i32, and checks the int that it is within a valid range. This is not a big function. But it might produce one of: 1. str::Utf8Error, 2. num::ParseIntError, or 3. MyCustomInBoundsError. There's no clean way to write a Rust function that could return either of them. I had to bundle everything up into an enum and then return that, and then the caller has to do some "match" acrobatics to handle each error differently.

I hate to say this, but I miss Python and C++'s exceptions. How nice to just try: something and then:

    except SomeError:
        doFoo()
    except ThatErrror:
        doBar()
    except AnotherError:
        doBaz()
    finally:
        sayGoodbye()
An elegant weapon for a more civilized age.

What do I know though? I'm still in the larval stage of Rust learning where I'm randomly adding &, *, .deref() and .clone() just to try to get the compiler to accept my code.

2 comments

You can still do that in rust if you want / need to:

https://play.rust-lang.org/?version=stable&mode=debug&editio...

    fn main() {
        match process(&[0x34, 0x32]) {
            Ok(n) => println!("{n} is the meaning of life"),
            Err(e) => {
                if e.is::<std::str::Utf8Error>() {
                    eprintln!("Failed to decode: {e}");
                } else if e.is::<std::num::ParseIntError>() {
                    eprintln!("Failed to parse: {e}");
                } else {
                    eprintln!("{e}");
                }
            }
        }
    }
    
    fn process(bytes: &[u8]) -> Result<i32, Box<dyn std::error::Error>> {
        let s = std::str::from_utf8(bytes)?;
        let n = s.parse()?;
        if n > 10 {
            return Err(format!("{n} is out of bounds").into())
        }
        Ok(n)
    }
In library code though that would make it generally more difficult to use the library, so the enum approach is more idiomatic. Then that comes out as

    match(e) {
        MyError::Decode(e) => { ... }
        MyError::ParseInt(e) => { ... }
        ...
    }
etc, which is isomorphic to the style you miss. What you're perhaps missing the is that `except ...` is the just a language keyword to match on types, but that Rust prefers to encode type information as values, so that keyword just isn't needed.

I feel you on the larval stage. Once you get past that, Rust starts to make a lot of sense.

Many error types implement std::error::Error, maybe using that would make things easier.

An example: https://play.rust-lang.org/?version=stable&mode=debug&editio...

Hmm, I think I tried this a few times, but I could never get the right magical combination of dyn, Box<> and & to get it to compile.