| ▲ | abtinf a day ago |
| Having programmed for over 30 years, including nearly a decade of C#, I would say exceptions are one of the worst ideas in all of programming. They are just horrific gotos that any library can invoke against your code. They are pretty much never, ever handled correctly. And nearly always, after an exception is “handled”, the application is actually in an unknown state and cannot be reasoned about. Even junior engineers have a trivial time debugging most go errors, while even experienced principles struggle with figuring out the true cause of a Java exception. |
|
| ▲ | mike_hearn a day ago | parent | next [-] |
| Well, I've also programmed for over thirty years and I wouldn't use a language without exceptions and even wrote a whole essay defending that position: https://blog.plan99.net/what-s-wrong-with-exceptions-nothing... > Even junior engineers have a trivial time debugging most go errors Not my experience at all. I had to do this once. An HTTP request to a production server was yielding a 400 Bad Request with no useful information for what was bad about it. No problem, I'll check the logs and look at the source code. Useless: the server was written in Go and the logs had no information about where the error was originating. It was just getting propagated up via return codes and not logged properly. It ended up being faster to blackbox reverse engineer the server. In a language with exceptions there'd have been a stack trace that pinpointed exactly where the error originated, the story of how it was handled, and the story of how the program got there. Diagnosing errors given stack traces is very easy. I've regularly diagnosed subtle bugs given just logs+stack trace and nothing else. I've also had to do the same for platforms that only have error codes that aren't Go (like Windows). It's much, much harder. |
| |
| ▲ | bob1029 a day ago | parent | next [-] | | > Diagnosing errors given stack traces is very easy. This is the most important aspect of exceptions in my view. The line that threw the exception isn't even the part of a stack trace that I find most interesting. The part that is most valuable to me when working on complex production systems are all of the call sites leading up to that point. I remember in my junior years I wasn't a big fan of exceptions. A stack trace would make my eyes glaze over. I would try/catch at really deep levels of abstraction and try to suppress errors too early. It took me a solid ~5 years before I was like "yes, exceptions are good and here's why". I think a lot of this boils down to experience and suffering the consequences of bad design enough times. | | |
| ▲ | mike_hearn a day ago | parent [-] | | Exception usability is definitely an area that needs work. If you work support for a dev platform for a while, it's a really common experience that people will post questions with a copy/pasted stack trace where the message+stack actually answers their question. You can just copy/paste parts back to them and they're happy. There's too much information and not enough tools to digest/simplify them and people get overwhelmed. Still, better to have too much data than too little. |
| |
| ▲ | 9rx a day ago | parent | prev | next [-] | | > Useless: the server was written in Go and the logs had no information about where the error was originating. [...] In a language with exceptions there'd have been a stack trace that pinpointed exactly where the error originated Go has support for exceptions, not to mention providing runtime access to stack trace information in general. They are most definitely there if your application requirements necessitate, which it seems yours did. Unfortunately, language support only helps if your developers actually speak the language. Go in particular seems especially prone to attracting developers who are familiar with other languages, who insist on continuing to try to write code in those other languages without consideration for how this one might be different and then blame the technology when things don't work out... | |
| ▲ | quotemstr a day ago | parent | prev [-] | | I can't agree with you about C++ exceptions being worse than useless. Exceptional C++ is worth it. Safety isn't that hard with RAII and ScopeGuard.. In your map example, just add a scope guard that removes the just-added element using the returned iterator if the rest of the procedure doesn't succeed. It's no different in Java. | | |
| ▲ | mike_hearn 10 hours ago | parent [-] | | Haven't seen ScopeGuard before but it looks like an implementation of defer() in C++? That sort of thing can help yes. But it's still way harder to get exception safety right in a language with manual memory management. In a GCd language you only have to be careful about cleaning up non-GCd resources, whereas in C++ you have to be ready for the expected lifetimes of things to be violated by exception unwinds at many different points and if you get it wrong, you corrupt the heap. Very few C++ codebases use exceptions vs all of them for Java, and I think that's why. | | |
| ▲ | quotemstr 2 hours ago | parent [-] | | A significant number of C++ codebases use exceptions. Google famously doesn't, but Meta, alike in dignity, does. GDB, now C++, does. At least some AI labs do. C++ exceptions are normal and common. |
|
|
|
|
| ▲ | rand_r a day ago | parent | prev | next [-] |
| > never handled correctly I’ve seen this argument, but if you look at real golang code and examples, it’s just a bunch of “if err <> nill” copy pasta on every line. It’s true that handling errors is painstaking, but nothing about golang makes that problem easier. It ends up being a manual, poor-man’s stack-trace with no real advantage over an automatically generated one like in Python. |
| |
| ▲ | swiftcoder a day ago | parent [-] | | Which could be solved in one swipe by adding a Result<T, Error> sum type, and a ? operator to the language. This is more a self-inflicted limitation of Go, then a general indictment of explicit error handling. | | |
| ▲ | rand_r 18 hours ago | parent [-] | | Nothing prevents explicit error handling in Python either. Forcing explicit error handling just creates verbosity since no system can functionally prevent you from ignoring errors. |
|
|
|
| ▲ | kaoD a day ago | parent | prev | next [-] |
| Many people against Go's error handling do not advocate for exceptions, but for a combination of an Either/Result type (for recoverable errors) and fully aborting (for unrecoverable errors). |
| |
| ▲ | OtomotO a day ago | parent [-] | | Abort on the other hand is used WAY to liberally in Rust. How I hate it, that every second function call can break my program when it's clearly not a "halt the world, it's totally unrecoverable that the user sent us nonsense" type. Return a Result and get on with your life! | | |
| ▲ | Chai-T-Rex a day ago | parent [-] | | If a Rust function can panic, there's generally a non-panicking alternative. For example, `Vec` indexing has `vec[n]` as the panicking version and `vec.get(n)` as the version that can return `None` when there's nothing at that index. | | |
| ▲ | mathw a day ago | parent | next [-] | | I do wish this is something Rust had done better though - the panicking versions often look more attractive and obvious to developers, and that's the wrong way round. Vec indexing, IMO, should return Option<T>. | | |
| ▲ | sshine a day ago | parent | next [-] | | While that is true, there are clippy::indexing_slicing, clippy::string_slice for that: https://github.com/rust-lang/rust-clippy/issues/8184#issuecomment-1003651774
error: indexing may panic
--> src/main.rs:100:57
|
100 | rtmp::header::BasicHeader::ID0 => u32::from(buffer[1]) + 64,
| ^^^^^^^^^
|
= help: consider using `.get(n)` or `.get_mut(n)` instead
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
| |
| ▲ | quotemstr a day ago | parent | prev [-] | | One of my dream projects is creating a Rust stdlib based solely on panics for error handling. Sure, it'd be incompatible with everything, but that might be a feature, not a bug. | | |
| ▲ | jll29 20 hours ago | parent | next [-] | | One of my dreams is for someone to create a Rust stdlib, full stop. I love Rust the language, but the current bazar of little bits of functionality scattered around in the form of a zoo of crates is such a mess compared to, say, Java's class library (I/O, data structures, std. algorthms for searching, sorting etc., arranged in a logically and hierarchially named way). I'm not against alternative implementations, but I'd rather have one "official" implementation that everyone knows that covers most cases and makes for the idiomatic reading of source code. | | |
| ▲ | OtomotO 18 hours ago | parent [-] | | No! I mean, yes, but not an official one. The stdlib is where good ideas go to die. Waiting for "pub struct RDBMSInferfaceV17ThisTimeWeGotItRightForSure" | | |
| ▲ | thesuperbigfrog 15 hours ago | parent [-] | | Firm disagree. Sylvain Kerkour described the problems Rust faces by having a limited standard library: "The time has come for Rust to graduate from a shadow employment program in Big Tech companies to a programming language empowering the masses of engineers (and not just "programmers") wanting to build efficient and robust sfotware. What crypto library should we use? ring, RustCrypto, rust-crypto (don't use it!), boring, aws-lc-s or openssl? Which HTTP framework? actix-web, axum, dropshot or hyper? What about a time library? time, chrono or jiff (how I'm even supposed to find this one)? You get it, if you are not constantly following the latest news, your code is already technical debt before it's even written. Fragmentation is exhausting. I just looked at the dependencies of a medium-sized project I'm working on, and we have 5+ (!) different crpyto libraries: 2 different versions of ring, aws-lc-rs, boring, and various libraries from RustCrypto. All of this because our various dependencies have picked a different one for their own cryptographic usage. This is insane, first because it introduces a lot of supply chain attack entry points, but also because there is no way that we will audit all of them" Source: https://kerkour.com/rust-stdx My moderate-sized Rust web service project requires 587 third-party crates which seems ridiculous. Fortunately, cargo makes it easy to manage the dependencies, but unfortunately I don't know how well supported or maintained the dependencies are. How well will they be maintained in five years from now? Will I need to find newer libraries and rewrite portions of my project to provide the same features that I have now? I don't know. |
|
| |
| ▲ | andrewshadura a day ago | parent | prev [-] | | I think someone's already created this. |
|
| |
| ▲ | OtomotO a day ago | parent | prev [-] | | in the stdlib, yes. In 3rd party crates? Depends! |
|
|
|
|
| ▲ | OtomotO a day ago | parent | prev | next [-] |
| But with Exceptions you can easily implement multiple return types in e.g. Java ;) I shocked my Professor at university with that statement. After I started laughing, he asked me more questions... still went away with a straight A ;D |
| |
| ▲ | Cthulhu_ a day ago | parent | next [-] | | As you should, it shows a deeper insight in the language beyond the base course material and out of the box (and cursed) thinking. | |
| ▲ | Yoric a day ago | parent | prev | next [-] | | In OCaml, that's actually a common use for exceptions, as they let you unwind extremely quickly and carry a result (helped by the fact that OCaml doesn't have finally or destructors). | |
| ▲ | sshine a day ago | parent | prev [-] | | When I took the compiler course at university, the professor would have a new coursework theme every year, and the year I took the course, the coursework compiler was exception-oriented. So exceptions were the only control flow mechanism besides function calls. If/else, while, return were all variations of throw. To me this proved that there's nothing inherently wrong about exceptions. It's how you structure your code and the implied assumptions you share with your colleagues. Some people are way too optimistic about their program will actually do. Happy path programming. | | |
| ▲ | BoiledCabbage a day ago | parent [-] | | Sounds interesting - any link to the control flow implementation online? Or how does one optionally throw and exception without an "if" statement? What's the "base" exception call? Ie if "if" is implemented via exceptions, how do exceptions get triggered? And is "while" done via an "if exception" and recursion? Or another way? |
|
|
|
| ▲ | pyrale a day ago | parent | prev | next [-] |
| Exception and explicit on-the-spot handling are not the only two ways to handle failing processes. Optional/result types wrapping the are a clean way to let devs handle errors, for instance, and chaining operations on them without handling errors at every step is pretty ergonomic. |
| |
| ▲ | quotemstr a day ago | parent [-] | | Rust's error handling evolution is hilarious. In the beginning, the language designers threw out exceptions --- mostly, I think, because Go was fashionable at the time. Then, slowly, Rust evolved various forms of syntactic sugar that transformed its explicit error returns into something reminiscent of exceptions. Once every return is a Result, every call a ?, and every error a yeet, what's the difference between your program and one with exceptions except the Result program being syntactically noisy and full of footguns? Better for a language to be exceptional from the start. Most code can fail, so fallibility should be the default. The proper response to failure is usually propagating it up the stack, so that should be the default too. What do you get? Exceptions. | | |
| ▲ | chrismorgan a day ago | parent | next [-] | | One practical benefit of Rust’s approach that hasn’t been emphasised enough yet is the consequences of Option<T> and Result<T, E> being just values, same as anything else. It means you can use things like result.map_err(|e| …) to transform an error from one type to another. (Though if there’s a suitable From conversion, and you’re going to return it, you can just write ?.) It means you can use option.ok_or(error) or option.ok_or_else(|| error) to convert a Some(T) into an Ok(T) and a None into an Err(E). It means you can .collect() an iterator of Result<T, E> into a Vec<Result<T, E>>, or (one of the more spectacular examples) a Result<Vec<T>, E> which is either Ok(items) or Err(first_error). It’s rather like I found expression-orientation, when I came to Rust from Python: at first I thought it a gimmick that didn’t actually change much, just let you omit the `return` keyword or so. But now, I’m always disappointed when I work in Python or JavaScript because statement-orientation is so limiting, and so much worse.¹ Similarly, from the outside you might not see the differences between exceptions and Rust-style Result-and-? handling, but I assure you, if you lean into it, it’s hard to go back. —⁂— ¹ I still kinda like Python, but it really painted itself into a corner, and I’ve become convinced that it chose the wrong corner in various important ways, ways that made total sense at the time, but our understanding of programming and software engineering has improved and no new general-purpose language should make such choices any more. It’s local-maximum sort of stuff. | | |
| ▲ | quotemstr a day ago | parent [-] | | Exceptions are values in C++, Java, and Python too. They're just values you throw. You can program these values. As usual, I find that opposition to exceptions is rooted in a misunderstanding of what exceptions really are | | |
| ▲ | chrismorgan a day ago | parent [-] | | Exceptions are values, but normal-value-or-exception (which is what Result<T, E> is) isn’t a value. Review my remarks about map_err, ok_or, &c. with the understanding that Result is handling both branches of the control flow, and you can work with both together, and you might be able to see it a bit more. Try looking at real code bases using these things, with things like heavily method-chained APIs (popular in JS, but exceptions ruin the entire thing, so such APIs in JS tend to just drop errors!). And try to imagine how the collect() forms I described could work, in an exceptions world: it can’t, elegantly; not in the slightest. Perhaps this also might be clearer: the fuss is not about the errors themselves being values, but about the entire error handling system being just values. |
|
| |
| ▲ | maleldil a day ago | parent | prev | next [-] | | > what's the difference between your program and one with exceptions Because errors as values are explicit. You're not forced to use ? everywhere; you can still process errors however you like, or return them directly to the calling function so they deal with it. They're not separate control flow like exceptions, and they're not a mess like Go's. | | |
| ▲ | quotemstr a day ago | parent [-] | | No, because you end up with a function coloring problem that way. A function that returns something other than Result has to either call only infallible code or panic on error, and since something can go wrong in most code, the whole codebase converges as time goes to infinity on having Result everywhere. Yeah, yeah, you can say it's explicit and you can handle it how you want and so on, but the overall effect is just a noisy spelling of exceptions with more runtime overhead and fewer features. | | |
| ▲ | dwattttt a day ago | parent | next [-] | | I very much care about whether a function can fail or not, and I encourage all the function colouring needed to convey that. | | |
| ▲ | funcDropShadow a day ago | parent [-] | | As almost always, we programmers / software developers / engineers, forget to state our assumptions. In closed-world, system-software, or low-level software you want to have your kind of knowledge about everything you call. Even more: can it block? In open-world, business-software, or high-level software it is often impossible or impractical to know all the ways in which a function or method can fail. What you need then, is a broad classification of errors or exception in the following two dimensions: 1. transient or permanent, 2. domain or technical. Those four categories are most of the time enough to know whether to return a 4xx or 5xx error or to retry in a moment or to write something into a log where a human will find it. Here, unchecked exceptions are hugely beneficial. Coincidentally, that is the domain of most Java software. Of course, these two groups of software systems are not distinct, there is a grey area in the middle. | | |
| ▲ | maleldil 4 hours ago | parent [-] | | Monadic error handling (such as Rust's) is a good compromise because it allows you to handle errors precisely when you want to _and_ bubble them up with minor boilerplate when you don't. You can even add context without unwrapping the whole thing. That's why it's often considered one of the better approaches: it has both the "errors as values" benefits (encoding errors in the type system) and avoids many of its problems (verbosity when you just want to bubble up). |
|
| |
| ▲ | Too a day ago | parent | prev | next [-] | | I think your conclusion is on the right track. Syntax sugar propagating results is in a way equivalent to exceptions. What you are missing it isn’t just equivalent to ordinary exceptions. It’s more equivalent to checked exceptions. A very powerful concept, that unfortunately got a bad rap, because the most widespread implementation of it (Java) was unreasonably verbose to use in practice. You might claim Rust is still too verbose and I don’t disagree with that, it’s a big improvement from Java at least, while at the same time providing even more safety nets. | | |
| ▲ | jll29 20 hours ago | parent [-] | | Yes, Java is too verbose, but Kotlins cleaned up much of that boilerplate and runs on the same VM. I'd be curious to see examples for where you think Rust is still to verbose. | | |
| ▲ | maleldil 4 hours ago | parent [-] | | You misunderstood. They meant that Java's checked exceptions are very verbose and that Rust's approach to errors is similar. While Rust's approach is less verbose, it's still more verbose than "regular" (unchecked exceptions). Kotlin isn't relevant here because all exceptions are unchecked. |
|
| |
| ▲ | high_na_euv 6 hours ago | parent | prev | next [-] | | "Function coloring problem" Function coloring isnt a problem, it is just approach | |
| ▲ | sunshowers a day ago | parent | prev | next [-] | | Function "coloring" is good! It's not a problem here and it's overblown as a problem in general. If something fails recoverably then it should be indicated as such. | |
| ▲ | pyrale a day ago | parent | prev [-] | | > A function that returns something other than Result has to either call only infallible code or panic on error ...Or solve the problem. A library function that can be a source of issues and can't fix these issues locally should simply not be returning something that is not a result in that paradigm. > since something can go wrong in most code That is not my experience. Separating e.g. business logic which can be described cleanly and e.g. API calls which don't is a clear improvement of a codebase. > the whole codebase converges as time goes to infinity on having Result everywhere. As I said previously, it is pretty easy to pipe a result value into a function that requires a non-result as input. This means your pure functions don't need to be colored. | | |
| ▲ | quotemstr a day ago | parent [-] | | > Or solve the problem. A library function that can be a source of issues and can't fix these issues locally should simply not be returning something that is not a result in that paradigm. People "solve" this problem by swallowing errors (if you're lucky, logging them) or by just panicking. It's the same problem that checked exceptions in Java have: the error type being part of the signature constrains implementation flexibility. | | |
| ▲ | ViewTrick1002 a day ago | parent [-] | | In my experience an unwrap, expect or panicking function is a direct comment in code review and won’t be merged without a reason explaining why panicking is acceptable. |
|
|
|
| |
| ▲ | zozbot234 a day ago | parent | prev | next [-] | | The '?' operator is the opposite of a footgun. The whole point of it is to be very explicit that the function call can potentially fail, in which case the error is propagated back to the caller. You can always choose to do something different by using Rust's extensive facilities for handling "Result" types instead of, or in addition to, using '?'. | |
| ▲ | pwdisswordfishz a day ago | parent | prev | next [-] | | In most languages with exceptions: • they may propagate automatically from any point in code, potentially breaking atomicity invariants and preventing forward progress, and have to be caught to be transformed or wrapped – Result requires an explicit operator for propagation and enables restoring invariants and transforming the error before it is propagated. • they are an implicit side-channel treated in the type system like an afterthought and at best opt-out (e.g. "noexcept") – Result is opt-in, visible in the return type, and a regular type like any other, so improvements to type system machinery apply to Result automatically. • try…catch is a non-expression statement, which means errors often cannot be pinpointed to a particular sub-expression – Result is a value like any other, and can be manipulated by match expressions in the exact place you obtain it. Sure, if you syntactically transform code in an exception-based language into Rust you won’t see a difference – but the point is to avoid structuring the code that way in the first place. | | |
| ▲ | quotemstr a day ago | parent [-] | | > they may propagate automatically from any point in code, potentially breaking atomicity invariants and preventing forward progress A failure can propagate in the same circumstances in a Rust program. First, Rust has panics, which are exceptions. Second, if any function you call returns Result, and if propagate any error to your caller with ?, you have the same from-anywhere control flow you're complaining about above. Programmers who can't maintain invariants in exceptional code can't maintain them at all. > try…catch is a non-expression statement, That's a language design choice. Some languages, like Common Lisp, have a catch that's also an expression. So what? > they are an implicit side-channel treated in the type system like an afterthought an Non-specific criticism. If your complaint is that exceptions don't appear in function signatures, you can design a language in which they do. The mechanism is called "checked exceptions" Amazing to me that the same people will laud Result because it lifts errors into signatures in Rust but hate checked exceptions because they lift errors into signatures in Java. Besides, in the real world, junior Rust programmers (and some senior ones who should be ashamed of themselves) just .unwrap().unwrap().unwrap(). I can abort on error in C too, LOL. | | |
| ▲ | Ygg2 20 hours ago | parent [-] | | > A failure can propagate in the same circumstances in a Rust program. What do you consider failure? A result or a panic? Those are worlds apart. > First, Rust has panics, which are exceptions. That's not how exceptions work in Java. Exceptions are meant to be caught (albeit rarely). Panics are meant to crash your program. They represent a violation of invariants that uphold the Safety checks. Only in extreme cases (iirc Linux kernel maintainers) was there a push to be able to either "catch panics" or offer a fallible version of many Rust operations. The Rust version of Java Exceptions are Results. And they are "checked" by default. That said, Exceptions in Java are huge, require gathering info and slow as molases compared to returning a value (granted you can do some smelly things like raising static exceptions)[1]. [1] https://shipilev.net/blog/2014/exceptional-performance/ > Non-specific criticism. If your complaint is that exceptions don't appear in function signatures, you can design a language in which they do. The mechanism is called "checked exceptions" And everyone hates checked exceptions. Because rather than having a nice pipe that encapsulates errors like Result, you list every minute Exception possible, which, if we followed the mantra of "every exception is a checked exception" would make, for example, Java signatures into an epic rivaling Iliad. > Besides, in the real world, junior Rust programmers (and some senior ones who should be ashamed of themselves) just .unwrap().unwrap().unwrap(). If this is a game who can write worse code, you'll never find Java wanting. As a senior Java dev, I've seen things... Drinking helps, but the horrors remain. |
|
| |
| ▲ | mottalli a day ago | parent | prev | next [-] | | Honest question: syntactically noisy as opposed to what? In the context of this post, which is a critique of Go as a programming language, for me this is orders of magnitude better than the "if err != nil {" approach of Go. | |
| ▲ | pyrale a day ago | parent | prev | next [-] | | I'm not sure why you bring up Rust here, plenty of libs/languages use the Result pattern. Your explanation of what bothers you with results seems to be focused on one specific way of handling the result, and not very clear on what the issue is exactly. > what's the difference between your program and one with exceptions Sometimes, in a language where performance matters, you want an error to be handled as an exception, there's nothing wrong with having that option. In other languages (e.g. Elm), using the same Result pattern would not give you that option, and force you to resolve the failure without ending the program, because the language's design goals are different (i.e. avoiding in-browser app crash is more important than performance). > syntactically noisy Yeah setting up semantics to make users aware of the potential failure and giving them options to solve them requires some syntax. In the context of a discussion about golang, which also requires a specific pattern of code to explicitly handle failures, I'm not sure what's your point here. > full of footguns I fail to see where there's a footgun here? Result forces you to acknowledge errors, which Go doesn't. That's the opposite of a footgun. | |
| ▲ | sunshowers a day ago | parent | prev | next [-] | | The big difference is API stability, one of the primary focuses of Rust. | |
| ▲ | Ygg2 a day ago | parent | prev [-] | | > Once every return is a Result, every call a ?, and every error a yeet The Try operator (`?`) is just a syntax sugar for return. You are free to ignore it. Just write the nested return. People like it for succinctness. Yeet? I don't understand, do you mean the unstable operator? Rust doesn't have errors either. > what's the difference between your program and one with exceptions except the Result program being syntactically noisy and full of footguns? Exceptions keep the stacktrace, and have to be caught. They behave similar to panics. If panics were heavy and could be caught. Rust errors aren't caught, they must be dealt with in whatever method invokes them. Try operator by being noisy, tells you - "Hey, you're potentially returning here". That's a feature. Having many return in method can both be a smell, or it could be fine. I can find what lines potentially return (by searching for `?`). An exception can be mostly ignored, until it bubbles up god knows where. THAT IS A HUGE FOOTGUN. In Java/C# every line in your program becomes a quiet return. You can't find what line returns because EVERY LINE CAN. |
|
|
|
| ▲ | bazoom42 a day ago | parent | prev | next [-] |
| Is “goto” just used to mean “bad and evil” here? Because exceptions are not a goto anymore than a return is a goto. The problem with goto is it can jump to any arbitrary place in your code. Exceptions will only go to catch-blocks up the call stack, which presumably you have written on purpose. |
| |
| ▲ | vaylian a day ago | parent [-] | | > Because exceptions are not a goto anymore than a return is a goto Not true at all * goto goes to a specific hard-coded address * return looks up the previous address from the stack and goes there * exceptions are a complex mess that require branching logic to determine where to resume execution | | |
|
|
| ▲ | Seb-C a day ago | parent | prev | next [-] |
| I agree about implicit exceptions, but I think that there is a sweet spot with explicit exceptions like Swift (and maybe Java): where you cannot not-handle one, it is part of a function's signature, and the syntax is still compact enough that it does not hurt readability. |
| |
| ▲ | therealdrag0 14 hours ago | parent [-] | | I think checked exceptions are noise. They usually never provide value, and are often just caught and rethrown as runtime exceptions to avoid the boilerplate. Scala did right to remove them. |
|
|
| ▲ | quotemstr a day ago | parent | prev [-] |
| Hard disagree. Exceptions are actually good. They make code clear and errors hard to ignore. I've written a ton of code over decades in both exceptional and explicit-error languages and I'll take the former every day. There's no function color problem. No syntactic pollution of logic with repetitive error propagation tokens. Also, exception systems usually come with built in stack trace support, "this error caused by this other error" support, debugger integration ("break the first time something goes wrong"), and tons of other useful features. (Common Lisp conditions are even better, but you can't have everything.) You can't just wave the word "goto" around as if it were self-evident that nonlocal flow control is bad. It isn't. > And nearly always, after an exception is “handled”, the application is actually in an unknown state and cannot be reasoned about. That's not been my experience at all. Writing exception safe code is a matter of using your language's TWR/disposable/RAII/etc. facility. A programmer who can't get this right is going to bungle explicit error handling too. Oh, and sum types? Have you read any real world Rust code? Junior developers just add unwrap() until things compile. The result is not only syntactic clutter, but also a program that just panics, which is throwing an exception, the first time something goes wrong. Many junior developers struggle with error handling in general. They'll ignore error codes. They'll unwrap sum types. They might... well, they'll propagate exceptions non-fat ally, because that's the syntactic default, and that's usually the right thing. We have to design languages with misuse in mind. |
| |
| ▲ | maleldil a day ago | parent | next [-] | | > Have you read any real world Rust code? Junior developers just add unwrap() until things compile. If you really don't like unwrap[1], you can enable a linter warning that will let you know about its uses to flag it during code review. You know exactly where they are and when they happen. Exceptions are hidden control flow, so you rely on documentation to know when a function throws. > Writing exception safe code is a matter of using your language's TWR/disposable/RAII/etc. facility. A programmer who can't get this right is going to bungle explicit error handling too. Rust has RAII, so you don't have to worry about clean-up when returning errors. This is a Go problem, not Rust. [1] https://blog.burntsushi.net/unwrap/ | |
| ▲ | OtomotO a day ago | parent | prev | next [-] | | Bah, no, I hated that you had to wrap basically every code block in a try/catch in Java, because the underlying lib could change and suddenly throw a Runtime-Exception. At the same time Checked Exceptions were a nightmare as well, because suddenly they were part of the contract, even though maybe wrong later. | | |
| ▲ | didntcheck a day ago | parent | next [-] | | > the underlying lib could change and suddenly throw a Runtime-Exception. And what would you do in that case? Since this is a future change your existing code presumably wouldn't know what else to do but throw its own exception, so why not just let that one propagate? | | | |
| ▲ | quotemstr a day ago | parent | prev [-] | | Checked exceptions are more trouble than they're worth. That doesn't make exceptions in general bad. | | |
| ▲ | mathw a day ago | parent | next [-] | | Not having checked exceptions is a huge problem, because then you never know when something might throw and what it might through, and in the .NET world the documentation on that is pretty awful and absolutely incomplete. But then over in Java world, your checked exception paradise (which it of course isn't because the syntax and toolkit for managing the things is so clunky) is easily broken by the number of unchecked exceptions which could be thrown from anything at any time and break your code in unexpected and exciting ways, so not only do you have to deal with that system you also don't get any assurance that it's even worth doing. But this doesn't actually mean checked exceptions are a bad idea, it means that Java didn't implement them very well (largely because it also has unchecked exceptions, and NullPointerException is unchecked because otherwise the burden of handling it would be hideous, but that comes down to reference types being nullable by default, which is a whole other barrel of pain they didn't have to do, and oh look, Go did the same thing wooo). | | |
| ▲ | neonsunset a day ago | parent [-] | | > in the .NET world the documentation on that is pretty awful and absolutely incomplete. Depends on the area you look at. Language documentation is pretty good and so is documentation for the standard library itself. Documentation for the frameworks can be hit or miss. EF Core is pretty well documented and it’s easy to find what to look for usually. GUI frameworks are more of a learning curve however. FWIW many in Java community consider checked exceptions to be a mistake. While I don’t find writing code that has many failure modes particularly fun with exception handling - Rust perfected the solution to this (and the Go way is visually abrasive, no thanks), I don’t think it’s particularly egregious either - Try pattern is pretty popular and idiomatic to use or implement, and business code often uses its own Result abstractions - switch expressions are pretty good at handling these. Personally, I’d write such code in F# instead which is a recent discovery I can’t believe so few know how good it is. |
| |
| ▲ | Cthulhu_ a day ago | parent | prev | next [-] | | What does make exceptions bad in my opinion (and shared by Go developers?) is a few things: 1. Exceptions are expensive (at least in Java / C#), as they generate a stack trace every time. Which is fine for actually exceptional situations, the equivalent of `panic()` in Go, but: 2. Exceptions are thrown for situations that are not exceptional, e.g. files that don't exist, database rows that don't exist, etc. Those are simple business logic cases. The workaround is defensive coding, check if the file exists first, check if the row exists? that kind of thing. 3. The inconsistency between checked and unchecked exceptions. 4. Distance - but this is developer / implementation specific - between calling a function that can throw an error and handling it. But #2 is the big one I think. Go's error handling is one solution, but if it's about correct code, then more functional languages that use the Either pattern or whatever it's called formally are even better. Go's approach is the more / most pragmatic of the options. | | |
| ▲ | cesarb a day ago | parent [-] | | > e.g. files that don't exist, database rows that don't exist, etc. [...] The workaround is defensive coding, check if the file exists first, check if the row exists? Ugh NO. Please don't. You should never "check if the file exists first". It can stop existing between your check and your later attempt at opening the file (the same with database rows). That can even lead to security issues. The name for that kind of programming mistake, as a vulnerability class, is TOCTOU (time-of-check to time-of-use). The correct way is always to try to do the operation in a single step, and handle the "does not exist" error return, be it a traditional error return (negative result with errno as ENOENT), a sum type (either the result or an error), or an exception. | | |
| ▲ | OtomotO a day ago | parent [-] | | Totally agreed, but as the previous poster wrote: an exception is meant for EXCEPTIONAL behavior. So it may be that the file access throws an exception but generally, I wouldn't agree. |
|
| |
| ▲ | OtomotO a day ago | parent | prev [-] | | As said, I don't like the wrapping of about everything with try/catch Sure, you can only do it way up the stack, but that's not enough quite often. If you can only do it all the way up, I find it ergonomic. Maybe I should experiment more with catch unwind in Rust. |
|
| |
| ▲ | biorach a day ago | parent | prev [-] | | > Oh, and sum types? Have you read any real world Rust code? Junior developers just add unwrap() until things compile. Junior developers will write suboptimal code in any language. So I'm not sure what your point is. |
|