6 ms·
> 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 sed
by burntsushi 10y ago
> 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.
- jackmott 10y ago"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.
- Coding_Cat 10y agoI 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.
- kazagistar 10y agoIn 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; }
- rspeer 10y agoSorry, 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?
- Rusky 10y agoYou 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.
- burntsushi 10y agoAs 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.ErrorKind.html 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.
- leshow 10y agoYou can use pre defined error types. Have you seen https://doc.rust-lang.org/std/io/enum.ErrorKind.html https://doc.rust-lang.org/std/io/enum.ErrorKind.html ?
- twic 10y agoIt 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.
- Rusky 10y agoI would love a `#[derive(From)]` for newtype structs and enum variants. Would bring Rust error handling back below Java in boilerplate levels. :)
- killercup 10y agoThere 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.
- look_lookatme 10y ago> 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.
- steveklabnik 10y agoWe 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.
- twic 10y agoThe 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!
- look_lookatme 10y agoYes, that's what I meant. Many ways to do things and those ways can differ significantly.