8 ms·
Rust error handling
- Animats 2y agoMaybe not perfect, but it seems to work out better than exceptions. Exceptions are a good idea which turned out to be too complicated. A language has to use destructors to clean up for almost everything for this to work. "?" has no "catch" clause within the function. So if an object has an invariant, and that invariant must be restored on error, the destructors must restore the invariant. If that just means unlocking locks, closing files, or deleting data structures, that works. If some more complex invariant needs to be restored, "?" isn't enough. The calling function has to clean things up. This usually means encapsulating the function that does the work in a function that handles errors and cleanup. Basically, a try block.
- hikarikuen 2y agoThis recent post resonated with me: https://cedardb.com/blog/exceptions_vs_errors/ https://cedardb.com/blog/exceptions_vs_errors/ There are certain obvious (and some less obvious) benefits to both exceptions and results, but I get the impression a lot of programmers have overreacted against exceptions. Exceptions "just work" the same in every codebase and require little boilerplate in most languages. I think results really shine for internal business logic where errors are more "invalid" than "exceptional."
- surajrmal 2y agoThat's what panics are for. They compile to the same code c++ exceptions would. You can unwrap your results to turn them into exceptions.
- WorkerBee28474 2y agoFor those who want to experiment with this style in C#, I've found this package to work: https://github.com/JohannesMoersch/Functional https://github.com/JohannesMoersch/Functional
- Yukonv 2y agoAnother good option I’ve personally used if you want a smaller API surface with just Result and Maybe concepts is True Myth. https://true-myth.github.io/true-myth-csharp/ https://true-myth.github.io/true-myth-csharp/
- brigadier132 2y agoIt's far from perfect. One of the biggest problems with Rust error handling is that if you want to have explicit error return types encoded in the type system you need to create ad hoc enums for each method that returns an error. If you only use a single error type for all functions you will inevitably have functions returning Results that contain error variants that the function will never actually return but still need to be handled. Without enum variants being explicit types and the ability to easily create anonymous enums using union and intersection operators Rust enums require a ton of boilerplate.
- wredue 2y agoYeah. The error type requires so much boilerplate that I honestly thought I was stupid and doing something wrong. But nope. Just horrific amounts of boilerplate. Then people who don’t want to engage with ThisError and Anyhow do bullshit hacks like making everything a string that has to be parsed (and don’t provide functions to parse). I get why it is that way, but it feels icky.
- brigadier132 2y ago> I get why it is that way, but it feels icky. There is no reason it has to be so boilerplate heavy, a lot of it can be fixed but someone has to put in the work. The only technical reason that could hold it back is compile times.
- eddd-ddde 2y agoZig has automatic error unions. No boilerplate at all, but not just a single "error" type. The only downside I see in zig errors is that they can't hold extra data.
- ziml77 2y agoIt's a massive downside. When I was using the JSON parser I found it very annoying that it could only tell me the input JSON was invalid, not where in the input the problem was.
- divan 2y ago"Go’s Error Handling Is Perfect, Actually" [1] https://blog.verygoodsoftwarenotvirus.ru/posts/errors-in-go/ https://blog.verygoodsoftwarenotvirus.ru/posts/errors-in-go/
- Buttons840 2y ago> Spend any amount of time in programming circles, and just as the sun rises and falls, you are certain to hear someone complain about error handling in Go. These complaints are, anecdotally, rarely well thought out suggestions on what error handling could or should be like in a language like Go, but often merely boil down to “I don’t like having to look at it”. I read it. The first paragraph dismisses preferences in a matter that boils down to preference. > Note that for any sufficiently complex program that invokes many dependencies, this stack trace will be so far down the chain that you may not even see where you’re making the call that causes it. I don't understand this part. Why would my code not appear in the stack trace? Did this author know how to read stack traces?
- danenania 2y agoPersonally I find it much faster to pinpoint errors in the Go style. With a stack trace, I have to cross-reference with code (ensuring versions match) and filter out a bunch of irrelevant calls in the stack. It’s not uncommon for the stack trace to end deep in library code with the root cause being many calls removed, making me check through a bunch of call sites to figure out what happened. In Go if good context is added to errors, an error log is generally enough on its own to make it obvious exactly what went wrong.
- Buttons840 2y agoAdding good context is also possible with exceptions. I think people often compare good Go code with, say, bad Python code where every good practice has been ignored. Go is new and people who write Go are more likely to be skilled and enthusiastic about its design philosophy, so it is somewhat true that Go code is more likely to be high quality. There's nothing Go error handling does that cannot be done with exceptions. When it comes to bad code, bad Go code is likely to ignore errors or have no context, whereas bad Python code is less likely to ignore errors and at least a raw stacktrace has some context. This is an argument as old as time though.
- blastonico 2y agoIsn't it based on ML family? I mean, I see Rust error handling heavy inspired in monads used in languages like OCaml and Haskell. Is Rust doing something different?
- pjmlp 2y agoAt least Haskell and OCaml also offer exceptions as alternatives.
- saurik 2y agoRust's error handling isn't at all like using a monad. The entire point of being able to express the monad for something like the behavior you expect from error handling is that you write code which automatically propagates the error and in the same breadth prevents you from ever being able to see the error. The result is essentially exactly exceptions: you program the happy path and it entirely hides errors from you, as that's the point of the monad, which for this purpose you can pretty much conceptualize as programmatic flow control (or, even more crudely, an overloaded semicolon / statement separator operator). Rust is using the data structure, but doesn't have a way to express or use the monad, so you have to deal with and manually propagate the error. But like, if you do not have the monad, the data structure is just awkward... the only reason the data structure for this monad even exists at all is to support the monad, and the reason for the monad is to get a syntax similar to exceptions! In a language with a ton of hard-coded syntax for all of these things you'd use a monad for in Haskell--whether it's error handling, asynchronous execution, scope allocation... whatever floats your boat--you should just use exceptions.
- tialaramex 2y agoYou can choose to work with Rust's Result in a monadic way, that's what methods like Result::and_then and Result::or_else and so on are for. Because it's just another type you could also do whatever else you like, unlike with the Exceptions in typical languages which have them where too bad, we bolted the information to the control flow so now we're going on a journey. If you want to bolt control flow to some information in Rust that's fine, feel free to define a function which returns ControlFlow::Break for success if that suits you, the try operator understands what you meant, early success is fine. Actually you can see this reflected in the larger language because break 'label value; exists in Rust unlike for example C++.
- pjmlp 2y agoExcept for anything more production quality, one needs to lean on third party crates to compose errors without explicitly write tons of boilerplate composing result types. Something that should be supported directly.
- spoiler 2y agoI agree that it's not ideal, but using something like anyhow/l and thiserror honestly doesn't feel _that_ bad. > Something that should be supported directly. Rust has "adapted" some crates into stdlib in the past. Are there any efforts to that for error handling?
- pjmlp 2y agoSo far only the related traits. That is the kind of excuse we give in C and C++ land, using static analysis isn't that bad.
- steveklabnik 2y agoWe're on the like, third (fourth?) generation of error handling crates, and while there's some degree of consensus happening, I'm not sure that it's time for it yet. If Rust had adopted error_chain into the stdlib, that would have been a huge mistake.
- _nalply 2y ago> But, since good programs don’t panic, and neither do good programmers, it’s very rare that using unwrap or expect is actually the right thing to do. I respectfully disagree. The assert!() macro is a way to document invariants. It's a bug if a variant is violated. It shouldn't have happened, but if it happens, then there's nothing the user can do except reporting the crash. The unwrap() and expect() method document the invariant that the None and Err() variants shouldn't occur. It's fail fast. You should use error handling only if users would be able to handle the errors. Tell the end user that the file they wanted to open is not readable, for example. Tell the users of your library that an error happened, like a parse error of a config file. And so on. Tell the users what they can fix themselves. A bug in your library or program, that's something different. Fail fast! And Rust panics are perfect for that.
- zarathustreal 2y agoI think you’re misunderstanding the “fail fast!” advice. You want to fail fast in development not in production. In production you want maximum robustness. Users would rather see an error saying “whoopsie, maybe try that again later” than for the program to exit. That’s part of the reason why the functional error handling patterns are becoming so popular these days. They force you to handle errors and give you type-level info about how the program can fail.
- edflsafoiewq 2y agoYou can catch panics.
- echelon 2y agoPanics are unexpected and not the preferred mechanism of error handling. Stick to Result<T,E>.
- edflsafoiewq 2y agoThe GP said > Users would rather see an error saying “whoopsie, maybe try that again later” than for the program to exit To achieve this, you need to catch panics. Rust does not use Result for things the OP is talking about like asserts.
- IshKebab 2y agoI don't know if I'd say it's perfect. I'd still like a way to break on `Err` in debuggers for example.
- XlA5vEKsMISoIln 2y ago.site-page.loading { opacity: unset; }