> 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.
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.
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:
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.
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
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:
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.
> 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.
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):
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.