8 ms·
Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do? You'd just concentrate on the "happy path", you'd cl
by adrianmsmith 2y ago
Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do?
You'd just concentrate on the "happy path", you'd close the file, there'd be nothing to forget or write blog posts about because the exception would be propagated, without needing to write any lines of code.
- iainmerrick 2y agoYou're not wrong, but that ship sailed away a dozen years ago.
- delusional 2y agoIf you've never seen somebody type 'catch (Exception e) { logger.log("should never happen", e);}' then sure. In the real world people will often explicitly ignore the error, even when they are confronted with it.
- devjab 2y agoExplicit error handling is a choice and implicit error handling through exceptions is not necessarily a feature. Both have advantages and disadvantages, I’d say the more “modern” approach actually the opposite to what you state here, and is in my opinion the way to Go (pun int intended), though it’s also how Haskell does it. You’ll find the same philosophy in Rust, Zig, Swift and others which all build on the previous decades of throwing exceptions and how terribly that scales in terms of maintainability. Even in the “old world” like with Java you have Kotlin which does both.
- saurik 2y agoI feel like you might be claiming that Haskell does something like Go does, but it actually doesn't: it supports monads, and so uses a monad to hide the error semantics entirely, providing exception-like syntax with automatic propagation. (I might misunderstand your use of "though", though? It could be that you were just noting in passing how Haskell disagrees with all of these supposedly-"modern" languages, and instead leaned into the sane happy path semantics, thanks to monads.) (edit: to be clear, though... I do not think exceptions solve this. I wrote a comment elsewhere on this thread about the semantics issue, but a few other people also wrote similar things while I was trying to type my overly-verbose reply ;P.)
- foldr 2y agoIt's worth noting that using Either style monadic error handling in IO code in Haskell is arguably an antipattern, as the Haskell runtime has its own built in exception handling (which actually works in a pretty conventional way). Some more info on this under 'Exceptions best practices ' here: https://tech.fpcomplete.com/haskell/tutorial/exceptions/ https://tech.fpcomplete.com/haskell/tutorial/exceptions/
- xg15 2y ago> Explicit error handling is a choice Yeah, it is - a bad choice IMO. I know the "if err != nil" pattern is spoken up as some sort of cultural idiosyncrasy of Go, similar to the whitespace formatting in python. But so far, I haven't seen any actual data (or even arguments) why it is superior to exceptions, or which inherent problems of exceptions it solves. (The classical example of "it makes control flow more obvious and makes it easier to ensure that mandatory finalizers are not skipped" was just disproven by this very article) So if there is more substantial criticism against exceptions than just FUD, I'd like to know it.
- randomdata 2y agoWell, let's assume Go did commonly make use of exception handlers for error cases: defer func() { if r := recover(); r != nil { fmt.Println("Failed to write file", r) } }() f := os.Create("file") defer f.Close() io.WriteString(f, "Hello, World!") Cool. You've solved one problem layer, perhaps. But if you look closely you'll notice that code still has a bug! So clearly exception handlers aren't enough. There might be some good ideas in using exception handling, but if you strictly limit its use to errors you end up half-assing it. Why not go all the way and find a solution that works in all cases?
- matt_craig 2y agoIs it that the defer() that recovers is called before the defer() that closes (and potentially fails)?
- randomdata 2y agoNo. No rearranging of the code would fix the problem.
- matt_craig 2y agoHmm. Checked the docs, looks like os.Create returns *File and err, but the sample doesn’t check that error.
- pjmlp 2y agoThe modern approach of Rust and Swift is basically checked exceptions done with a different painting.
- kaba0 2y agoGo is doing the worst thing possible. It is neither expressive enough for proper sum types, nor does it have expressions (that are analogous to sum types with good defaults and syntactic sugar). It is literally C’s shitty `errno` with syntactic sugar.
- kbolino 2y agoThere is a lot of space between the spooky magic quasi-global errno integer and proper sum types. Go's errors are not magic globals nor are they mere integers, even if they aren't sum types either.
- kaba0 2y agoHow is it not just errno? Especially that POSIX mandates errno to be thread-local, so not even that is a difference. Just because there is some syntactic sugar that converts it to a slightly more descriptive type than an int, doesn’t make it different.
- kbolino 2y agoThread locals are not lexically scoped, they are not stored as part of the function call stack, and mutating them is not expressed anywhere in the function signature. They are global variables with thread-local storage, not local variables. Go's error returns are not sum types, but they are product types. The return signature (T, error) indicates that two values will be returned essentially as a tuple by the function: one of type T and one of type error. Error-returning functions are pure functions (though they typically perform other, impure operations). There is no syntactic sugar (both for good and for ill). The type of errors is an ordinary interface, with a single method. Any type can implement that interface, including strings and structs and slices. Errors can have as many contextual details as needed, including nested/wrapped error messages, specific parameters of loop iterations, multiple errors rolled up from multiple operations, etc. Go's error return is just an ordinary but common use of its multiple-value returns. You can write a function/method that returns three ints and no errors: func (v Vector3) Splay() (x int, y int, z int) You can even write a function that returns multiple errors: func DoThisAndThat() (thisError error, thatError error) Try that with errno!
- randomdata 2y agoGo does indeed has an exception handling system like most other languages. Errors and exceptions are very different things, though. Of course, nothing stops you from building your own file handing package that overloads exception handlers to deal with errors. If it gains traction then it would prove the stdlib should consider a v2 API. But that you already haven’t done so is telling…
- peterashford 2y agoYou mean panics? They don't work across go routines so they're limited and hardly exception handling like most other languages
- randomdata 2y agoWhich languages see exception handlers work across go routines? Anyway, most don't. There is no difference from exception handlers in most other languages. The syntax is a little different. Is that where you've become confused?
- rerdavies 2y agoLanguages in which try/catch/throw work across coroutine boundaries: Java, Javascript, and it's a standard feature in C++ coroutine libraries (and completely supported by the core C++ coroutine engine). So in my limited personal experience, among the languages that I am familiar with ... all of them except go. Are there actually ANY languages other than go that have coroutines, and try/catch/throw mechanisms, where you cannot throw across a coroutine boundary? And why would exception handlers NOT work across coroutine boundaries, other than laziness on the part of implementers?
- randomdata 2y ago> Languages in which try/catch/throw work across coroutine boundaries You wha...? The question was about goroutines, not coroutines. Besides, you'll notice that exception handlers cross coroutine boundaries in Go just fine. Your random tangent isn't even correct. Where did you dream up this idea to the contrary? I know coroutines are still new to Go, only officially landing in the latest release (experimentally in 1.22), but you'd think that would also mean their behaviour is fresh in your memory. I'll take your avoidance of the original question to mean that no other language does it either.
- cyco130 2y agoThis problem isn't solved with exceptions either. The problem is that finalizers (C++ destructors, Java's `finally` blocks, Go's `defer` etc.) shouldn't fail but `close()` can fail. Therefore, for 100% correctness, `close()` calls should be handled explicitly and not left to finalizers. Finalizers shouldn't fail because they might be executed while another exception is already in flight. Three languages have three different behaviors when that happens but in my opinion they all do the wrong thing: In C++, if you throw from a destructor while another exception is in flight, the program will be terminated. In Java, throwing from a `finally` block will "forget" the original exception. In Go (according to this article, I'm not familiar with it), error from `defer` will be ignored. None of these are ideal.
- pansa2 2y ago> in my opinion they all do the wrong thing What would be the right thing? Combining the original exception and the error from `close` into some kind of `MultipleError`?
- saurik 2y agoPersonally I do not believe the math of these two monads allows for any better solutions (and I do not believe multierror is correct ;P)... I am thereby also very curious what they think the correct thing to do here is.
- cyco130 2y agoThat's probably the best option I think. I've heard Ada does that (but don't quote me on that). If you can access the original errors from the `MultipleError` object, at least you can tell the user what exactly went wrong. I don't thing there's one true right thing™ though. That's why explicit handling is necessary: The compiler doesn't have enough context to handle it for you. The programmer needs to decide what's the right way to handle it.
- Almondsetat 2y agoWhat about Java's try-with-resources?
- TheTrueScotsman 2y ago[dead]
- louthy 2y ago> Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do? Or monads, but that might be a step too far for the Go world considering the push-back against generics. When you have declarative error handling (a good thing imho) then monads really are the bees knees.
- thiht 2y agoExceptions are a terrible error handling mechanism. You have no idea what throws and what doesn’t, it’s impossible to write defensive code that makes sense with exceptions. Errors as value is the only sane way to deal with errors. Granted, Go does it pretty badly but it’s still infinitely better than exceptions.
- adrianmsmith 2y ago> You have no idea what throws and what doesn’t The answer here is that everything throws. Any code can have a Null/Nil dereference error, any code can use an array and generate an out-of-bounds exception, etc.
- skitter 2y agoWhen there's a null pointer dereference, that's a bug in the program; when closing a file fails, that's something external the program must handle.
- DarkNova6 2y ago> You have no idea what throws and what doesn’t You do in Java. It's called Checked Exceptions. It's a binding API contract and communicates this well not only when writing the code, but also when reviewing it.