10 ms·
I spent today updating some production Rust code from 2016. This was a partly automated process thanks to 'cargo fix'. Some thoughts: 1. Most of the superfici
by ekidd 7y ago
I spent today updating some production Rust code from 2016. This was a partly automated process thanks to 'cargo fix'.
Some thoughts:
1. Most of the superficial changes, like 'dyn' and Rust 2018, are easily handled using automatic tools.
2. The original Rust 'Error' trait was clunky, and it lacked useful features like backtrace support. This led to a series of experiments and fads in error handling (e.g., error-chain, failure), which are only starting to calm down after recent enhancements to 'Error'. More conservative Rust users avoided this by sticking with 'Error' and living with the limitations.
3. Updating depencies has been surprisingly easy, with several notable exceptions. Very early adopters of 'tokio' 0.1 had a fair bit of work to do once 'async' stabilized. And OpenSSL has been a constant thorn in my side, despite being a native C dependency.
One handy tip: Don't force yourself to constantly update your code to use all the latest features. Not everything needs to be async. And it's OK to have some rarely-touched modules that are written like it's 2015.
- oconnor663 7y ago> And it's OK to have some rarely-touched modules that are written like it's 2015. Indeed, the Rust compiler team has done a ton of work to build the "editions" system, so that you can do just that.
- d1plo1d 7y agoAs someone newer to Rust who has been using error-chain because it was what I found at the time I'd be curious to hear what your preferred solution to errors in modern rust is.
- steveklabnik 7y agohttps://blog.yoshuawuyts.com/error-handling-survey/ https://blog.yoshuawuyts.com/error-handling-survey/ is a good summary of what's out there. I think current rough consensus is anyhow/thiserr depending on if you're writing an application or library. I haven't actually used them myself, though. You don't have to keep up with the cool new libraries.
- matklad 7y agoAgree that there’s consensus on anyhow for applications, but I think many folks now prefer vanilla std::error::Error for libraries (which itself got better as a result of all the experiments in the ecosystem).
- littlestymaar 7y agoThe good thing with this_error is that it's just a custom derive on vanilla std::error::Error.
- Arnavion 7y agoI used to maintain a custom derive crate for errors before failure came out, but these days I just use manual impls of `std::error::Error` for libraries and `Box<dyn std::error::Error>` for applications. I can't be bothered to keep up with the Rust error-library-du-jour game, even if it so happens that today's darling custom derive (thiserror) would emit the same `std::error::Error` impl that I write by hand. Manual impls may be tedious to write, but they change rarely after they've been written, enough that the con of adding one more dependency that may go away tomorrow outweighs the con of writing them manually.
- jfkebwjsbx 7y agoThis matches my experience as well. I want to write code that works for years (dependencies make this hard) and is understood by everyone. So explicit is best.
- ShorsHammer 7y agoFailure is really nice to use. Might suit some people. https://github.com/rust-lang-nursery/failure https://github.com/rust-lang-nursery/failure
- bschwindHN 7y agoI wouldn't recommend failure, its current maintainer and original author will be deprecating it: https://boats.gitlab.io/blog/post/failure-to-fehler/ https://boats.gitlab.io/blog/post/failure-to-fehler/ From the article: > The crate I would recommend to anyone who likes failure’s API is anyhow, which basically provides the failure::Error type (a fancy trait object), but based on the std Error trait instead of on.
- charliesome 7y agoAs the kind of more conservative Rust user GP references, I've had a great time with the derive_more crate's From derive [0]. This lets me write code like this: #[derive(From, Debug)] pub enum SomeSpecificError { Io(io::Error), SomeAppLevelErrorCondition, } fn do_something() -> Result<(), SomeSpecificError> { do_some_io()?; // ... } Previously I would have written: fn do_something() -> Result<(), SomeSpecificError> { do_some_io().map_err(SomeSpecificError::Io)?; } I find this approach results in clear code that continues to compile and work just fine over time, even as everything changes around it. [0] https://jeltef.github.io/derive_more/derive_more/from.html#enums https://jeltef.github.io/derive_more/derive_more/from.html#e...
- Arnavion 7y agoUnless you want all I/O errors to be indistinguishable to your callers, you should use more specific variants than a single `Io(std::io::Error)`, like `ReadConfig(std::path::PathBuf, std::io::Error)` and `ConnectToServer(std::net::SocketAddr, std::io::Error)`. And if you do so, the `From` impl becomes untenable and you have to fall back to `.map_err(...)?`. In general, I feel `?`'s automatic `From::from` conversion leads to bad errors, because the programmer is incentivized to have type-specific context via implementing `From<>` rather than operation-specific context, even though operation-specific context leads to better error messages. Consider the difference between: failed to initialize library caused by: an I/O error occurred caused by: no such file or directory (2) and failed to initialize library caused by: could not read config file at /path/to/config.toml caused by: no such file or directory (2) Edit: I should add that it is possible to use `From<>` when the type-specific contexts and operation-specific contexts form a bijection. In the above example, if there were dedicated `struct ReadConfigError(std::path::PathBuf, std::io::Error)` and `struct ConnectToServerError(std::net::SocketAddr, std::io::Error)` types, then having the higher-level `Error` impl `From<ReadConfigError>` and `From<ConnectToServerError>` would be perfectly fine. Of course, that would just shift the issue down to those types since they would not be able to impl `From<std::io::Error>`. Also, in my experience, libraries rarely bother having such individual operation-specific error types anyway.
- 7y ago
- saghm 7y ago> And OpenSSL has been a constant thorn in my side, despite being a native C dependency If it's acceptable for your use case, it might be worth taking a look at rustls. I'm not sure how far along it was back in 2016, but it's in a pretty good state right now. I consider it much easier to deal with than OpenSSL, and the fact that a lot of legacy crypto that I have no need for isn't even supported in the library is appealing at least to me.
- hinkley 7y agoI feel like error handling in Node hit a nadir in 10.0 due to async function stack traces being so truncated. I started in the industry when Java was young and while I appreciated stack traces plenty, I hardly ever had to deal again without them, and you really do come to depend quite a lot on this behavior. Officially we are upgrading to 12 for perf, but I was the first (and will probably be the primary) contributor to that work because I want everyone to have decent stack traces again. Our code has too many layers of abstraction, and working without full traces is adding insult to injury.