Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> There's a thread in /r/rust about this same article where you can look and see people suggesting all sorts of ways to write this that are split into clear sedimentary layers depending on when the writer learned the language.

As someone that participated in that conversation, I think that's a pretty inaccurate characterization of it. It's not about when the writer learned the language, but rather, what problem you're trying to solve. If you'll allow me to summarize very briefly (perhaps at the expense of 100% accurary):

    * Use unwrap/expect when you don't care.
    * Use `try!`/`?` with Box<Error> in simple CLI applications.
    * Use `try!`/`?` with a custom error type and From impls in libraries.
    * Use combinators (e.g., map_err) when you need more explicit control.
You might imagine that you could use any number of these strategies depending on what you're trying to do, which might range from "a short script for personal use" to "production grade reliability."

All of this stuff was available at Rust 1.0. (Except for `?`, which is today an alias to `try!`.) It all falls out of the same fundamental building blocks: an `Error` trait with appropriate `From` impls.

The one exception to this is that, recently, there has been a surge in use of crates like error-chain to cut down on the code you need to write for defining custom error types and their corresponding `From` impls. But it's still all built on the same fundamental building blocks.



"new idioms and special syntax that is alien to pretty much everyone"

->

"an `Error` trait with appropriate `From` impls."

Note, that it may be entirely necessary for us to invent new idioms to make progress in the art of programming.


I will say that as someone who was not a real expert at anything when Rust came out, but familiar with a lot, I find that learning c++ these days has the same problems, and so does learning a functional language.

They're all unique in what they do. I'd still say a simple procedural language like C is easiest to read, but 'modern c++' has more idioms and quirks than Rust, in my experience.

And Rust has the advantage that the ecosystem is very well tied together and it has a great community and documentation.


It might be worth pointing out that this Rust:

    #[derive(Debug)]
    enum ConfigError {
        Io(io::Error),
        Parse(ParseIntError),
    }
    
    impl From<io::Error> for ConfigError {
        fn from(err: io::Error) -> ConfigError {
            ConfigError::Io(err)
        }
    }
    
    impl From<ParseIntError> for ConfigError {
        fn from(err: ParseIntError) -> ConfigError {
            ConfigError::Parse(err)
        }
    }
    
    fn read_config() -> Result<i32, ConfigError> {
        Result::Ok(parse_int(read_config_file()?)?)
    }
    
    // given the following
    fn parse_int(str: String) -> Result<i32, ParseIntError> { ... }
    fn read_config_file() -> Result<String, io::Error> { ... }
Is, in a sense, the equivalent of this Java:

    int readConfig() throws IOException, ParseException {
      return parseInt(readConfigFile());
    }
    
    // given the following
    int parseInt(String str) throws ParseException { ... }
    String readConfigFile() throws IOException { ... }
The reason i say that is that this:

    throws IOException, ParseException
Is essentially a sum type. It says that if this method results in a failure value, it can fail with one of two types of failure values. It might not look like a new type, because in Java, types are almost always nominal, and this is structural, but that's what it is. I think that throw and catch clauses are the only place that Java will let you define an ad-hoc sum type. You have to use polymorphism everywhere else you want a variety of types.

Whereas in Rust, there are no structural sum types, and so the only way to make something resembling a sum type is:

    enum ConfigError {
        Io(io::Error),
        Parse(ParseIntError),
    }
Which means you also have to write the machinery to convert between the types.

I wonder if it would help to have a compiler- or library-defined From impl for all newtype enum variants (or all newtype structs more generally), that makes the variant from its argument. Or maybe it could be derived. It would wipe out a lot of this boilerplate.


I would love a `#[derive(From)]` for newtype structs and enum variants. Would bring Rust error handling back below Java in boilerplate levels. :)


There are some crates that add a derive for errors, but most people tend to use error-chain or quick-error. Both give you the boilerplate pretty much for free, with various other advantages. E.g., using error-chain, you can just write the above code as

    error_chain! {
        foreign_links {
            Io(io::Error);
            Parse(ParseIntError);
        }
    }
and it'll even generate an aliased `Result` type as well as fancy chaining support.


Right- error_chain is just a bit too much magic for me compared to what #[derive(From)] would do.



Very nice. Now we just need enum variants to be first-class types, and some syntax for deriving on them.


In case someone cares to understand what this means:

- "An Error trait" means that when you define a new type that will store error information, you have to define how it implements the Error interface.

- "appropriate From impls"... you are trying to wrap a number of error types in your own special error type, you need to tell the compiler how to convert another specific type into your type new type. There is an interface (trait) in the standard library for this purpose called "From". This is done as an alternative to inheritance in an error system. The trait signature looks like this:

    trait From<T> {
        fn from(T) -> Self;
    }


Sorry, I'm trying very hard to learn Rust, and I'm willing to accept a lot of its restrictions, but this didn't clear anything up.

Why does every library implement its own Error type? It feels like reinventing the wheel, and it takes a lot of boilerplate code.

If you need to distinguish different kinds of errors, why not, for example, have a lot of useful pre-defined error types like Python does?


You can definitely reuse other library's error types. I often do that while prototyping.

The benefit of lib-specific error types, though, is that they can be far more specific.


As an example of the kind of specificity you might want, take a look at the error type for parsing a regular expression: https://docs.rs/regex-syntax/0.4.0/regex_syntax/enum.ErrorKi...

Now, in most cases, callers don't care at all about which specific error happened. They just want to print the error and be done with it. And that works just fine, because of the error handling machinery. But if you do want to drill down, then the option is there for you.


You can use pre defined error types. Have you seen https://doc.rust-lang.org/std/io/enum.ErrorKind.html ?


That seems to just be kinds of IO errors, though.


> The one exception to this is that, recently, there has been a surge in use of crates like error-chain to cut down on the code you need to write for defining custom error types and their corresponding `From` impls. But it's still all built on the same fundamental building blocks.

One takeaway I got from playing around with Rust (and using GitHub code search to work through my issues) was that coding style will probably differ considerably from project to project, which is very C/C++ like and maybe a good thing for the language, but I found a little disappointing.


We are actively working on rustfmt, which should add some consistency overall. Some people won't use it, of course, but many have said they will.


The kind of 'coding style' that is varying here is probably more in the domain of Clippy than rustfmt. But there are people working on that too!


Yes, that's what I meant. Many ways to do things and those ways can differ significantly.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: