4 ms·
"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 nece
by jackmott 9y 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 9y 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 9y 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 9y 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 9y 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 9y 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 9y 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 ?
- rspeer 9y agoThat seems to just be kinds of IO errors, though.
- twic 9y 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 9y agoI would love a `#[derive(From)]` for newtype structs and enum variants. Would bring Rust error handling back below Java in boilerplate levels. :)
- killercup 9y 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.