6 ms·
I'm genuinely hoping that after generics, the focus will be on error handling from the survey results Go devs in a lot of communities love to put their heads i
by aniforprez 4y ago
I'm genuinely hoping that after generics, the focus will be on error handling from the survey results
Go devs in a lot of communities love to put their heads in the sand going "error handling is perfectly fine". But it's not fine. Not even close. And I'm very happy there's a lot of demand for some solution in this space. It's just plain too verbose without any of the actual benefits of that verbosity. You don't have compile time checks for errors. Ignoring errors continues to be incredibly easy. The lack of exhaustive switch case statements makes this worse because the compiler doesn't care if you're handling all the expected error types (or unexpected ones). If you simply want to push errors up the chain to be handled elsewhere you're adding 3 lines of code in every function that just does `if err != nil { return nil, err }`. This much verbosity means almost every developer's eyes just glaze over all this code during review which I find to be quite dangerous. If this one thing is adequately fixed in Go, it wouldn't make the language perfect but it would probably make for the most pleasant development experience for building APIs and services
- pipe_connector 4y agoIf I'm reading your feedback correctly it sounds like you want: 1) Syntactic sugar for if err != nil { return err }; similar to ? in Rust 2) The errcheck linter to be enforced (part of go vet, maybe?) 3) Exhaustive switch statements The first point was looked at and no one has proposed a solution enough of the community could agree was an improvement while maintaining sufficient explicitness. If someone has a really good proposal for this I am sure it would be considered. The second point could probably happen with enough lobbying. So, verbosity is still there but at least every project will do something with their errors. The third point I don't believe is possible today because of the way interfaces work. If Go added sum types then this should be possible, but I could be mistaken about that.
- aniforprez 4y ago1) Yes I didn't want to make the tired comparisons to Rust but Rust does have the excellent syntactic sugar for returning errors. Of course, something like this is hampered by the lack of sum types and the ambiguous zero values in Go which makes it more difficult for the compiler to know what to return but I hope we eventually get there or we find another way to do this 2) I'm not a fan of leaving error checking to linting. It would be fantastic if the compiler would stop for mishandled errors. It already doesn't compile for banal things like unused imports and variables 3) It's not really a matter of wanting exhaustive switch statements as such but if this was present, it would make error handling much simpler, again a la Rust (I'm sorry) These are all hopes and wishes to make the language better to work with and I'm sure if we get anything close to any of this, it would be a while. The Go governance seems to move very slowly and deliberately considering how long it took them to wake up to adding generics but we can still dream
- cesarb 4y ago> > Syntactic sugar for if err != nil { return err }; similar to ? in Rust > Yes I didn't want to make the tired comparisons to Rust but Rust does have the excellent syntactic sugar for returning errors. Many people seem to forget that Rust didn't start (even in its 1.0 release) with the ? operator. It started with the try!() macro, which expanded to something not unlike Go's "if err != nil { return err }". The ? operator was added later, as a shortcut to the same semantics as the macro except for also being able to work with Option instead of just Result, based on developer experience with the macro (the two major annoyances being not being able to use the macro with Option, and the nesting when chaining several uses of the macro). If Rust was able to create the ? operator based on developer experience with its try!() macro, I don't see why Go won't be able to create its own error propagation operator based on developer experience with its "if err != nil { return err }" idioms.
- masklinn 4y ago> If Rust was able to create the ? operator based on developer experience with its try!() macro, I don't see why Go won't be able to create its own error propagation operator based on developer experience with its "if err != nil { return err }" idioms. Because Go does not have declarative macros. So while Rust got something like 25 versions out of try! before considering that it’s worth an operator (but it could well have gone with an other pattern if one had arisen) that’s not really an option for go.
- eikenberry 4y ago> 1) Syntactic sugar for if err != nil { return err }; similar to ? in Rust IMO they could exactly use what you wrote, ie. change gofmt to have it all on one line like that. The biggest annoyance with the error return boilerplate is the waste of vertical space and just squashing it into 1 line would fix that.
- metaltyphoon 4y agoOr perhaps allow variable assignment to be used in the same scope. if x, err := someMethod(); err != nil { return err } x <- is usable here.
- klodolph 4y agoI would love to see some in-depth discussion about this. Even in 2022, I don't think it's clear what we want error handling to look like. You can ask for correctness guarantees from the compiler, but it's easy to ask, it's harder to design a system that actually works. > The lack of exhaustive switch case statements makes this worse because the compiler doesn't care if you're handling all the expected error types (or unexpected ones). I would love to see how this is supposed to work. My first thought here is checked exceptions in Java. With checked exceptions, you have a specific set of exceptions that a function is supposed to generate, and a compile-time error if those exceptions are not either handled or propagated. This turned out to be a bit of a disaster in actual practice, and I'm not sure what the fix would look like here. I'm generally a bit miserable when I try to deal with error handling in Rust, believe it or not. As for the err != nil boilerplate, I've seen some proposals but I haven't seen anything that was clearly a good proposal. The generics proposal was clearly good, IMO, just for a point of reference.
- gregwebs 4y agoIn Rust you can use the anyhow library if you want a similar level of overhead to Go and similarly aren’t worried about differentiating error cases. What do you think of adopting Zig’s error handling approaches to Go?
- klodolph 4y ago> [...] and similarly aren’t worried about differentiating error cases. I differentiate error cases in Go, so I'm not sure what you're talking about. Sometimes it's done with ==, other times it's done with type assertions, type switches, and some helper functions in the errors package. I've barely touched Zig. I think it's contributing some important ideas, and maybe those ideas will get further developed in newer languages.
- indemnity 4y ago> I'm generally a bit miserable when I try to deal with error handling in Rust, believe it or not. Likewise. I'm a big Rust user, use it for all my personal projects, but error handling is totally unsolved there. - Friction around mapping between error types if I need to "add context and rethrow" - Often lack of stack/trace information for custom error types. Something went wrong the the DB query? Great! Where? Which line of code? There are solutions for these, I try them, and I'm sure someone will pipe up mentioning them (e.g. anyhow, thiserror, miette, etc), but its just so much damn ceremony to do it properly at a particular call site, especially when you are mixing your own code with library code and each library's unique snowflake error types, that I don't do it consistently.
- H1Supreme 4y ago> You don't have compile time checks for errors. I don't quite understand this statement. The compiler finds errors in my code all the time. Should it be evaluating something like `if err != nil`?
- metaltyphoon 4y agoI guess what he is saying is that x, _ := someFunc() shouldn't compile if someFunc has error returns.
- H1Supreme 4y agoAh, that makes sense. I wonder if a compiler flag would be a good compromise? As a way to report unhandled errors, but still let it compile.
- jerf 4y agoThat syntax basically means "I am deliberately not handling error returns". I always pair it with a comment that explains why I can do that. It comes up. A common one for me for instance is using .Write([]byte) (int, error) on a bytes.Buffer. Because that .Write implements an interface that has an error return, there's an error return, but a byte buffer can never fail to .Write. (If it does due to memory running out, I don't think it ever comes back as an error, just the process terminating.) There's no point being more particular about it... there's no practical way to force a programmer to handle an error, especially in light of the fact that "do nothing with the error" is also a perfectly acceptable thing to do! (Example: Writing a JSON document out as the result of an HTTP API call. I don't want to log every such error, because that just crufts up my logs, I don't need metrics on this particular system, and once the HTTP stream is broken there's nowhere left to write an error out to the user, so... discard it is.)
- deleted 4y ago[deleted]
- nemothekid 4y agoA real problem is when something like this occurs x, err := thisFuncFails(); // some other lines of code, but err is not checked x.callMethod(); // this crashes at runtime unless you explicitly ignore `err`, the compiler shouldn't allow you to use `x` without checking err. In Rust/Zig this is accomplished with the union types, which doesn't allow to use `x` until you have checked it's not an error. This is also very useful for Option types; and I wish it was in Go since the beginning as having `nil` in the language is a huge mistake.
- gregwebs 4y agoI am quite convinced that Zig's approach to error handling would apply well to Golang. At least adding a "try" prefix would be an easy change. Add the ability to enable/disable an error return trace and that would do a good enough job handling the majority of error handling boilerplate. * https://ziglang.org/documentation/master/#try * https://ziglang.org/documentation/master/#Error-Return-Traces I am working on adding this to the compiler now but I know of no actual process for getting such an improvement officially into the Go language. There are several other concepts in Zig that enhance safety that Go can consider adopting. I personally prefer the soundness of Rust, but Zig is much more philosophically aligned with Go. In error handling alone, we can consider adopting a "catch" suffix as Zig has for when "try" is insufficient as well as error sets. Additionally optional types seem like they would work well in Go. * https://ziglang.org/documentation/master/#Optionals * https://ziglang.org/documentation/master/#Error-Set-Type
- bheadmaster 4y ago> You don't have compile time checks for errors. Doesn't the fact that errors are values, combined with the fact that you cannot ignore values (except explicitly by assigning them to "_"), provide compile time check for errors?
- teraflop 4y agoNot really. It's idiomatic to use a single "err" variable for every error value in the same function body, and the compiler will only check whether that variable is used at least once; it doesn't perform a data flow analysis to ensure that every value that might be assigned is used. So if you assign 10 different error results to the same variable at various points, but only actually check it 9 times and forget the 10th, the compiler will not tell you that anything's wrong.
- bheadmaster 4y agoYou are right. I forgot about that case. Perhaps variable re-declarations (via := operator) would be a good addition to Go. Compiler could optimize the code to re-use the variables when possible, but the semantics of the language would force the programmer to handle each and every error.
- stouset 4y agoNot really. Go thinks this is fine (on mobile, forgive me if I get this wrong). foo, err := foo() if err != nil { return nil, err } bar, err := bar(foo) return bar, nil The problem occurs because go only cares if a variable is used once per definition. Not once per assignment.
- bheadmaster 4y agoYou are right, I have missed that, as I have written under the sibling comment. Though I'd like to ask - does that scenario really happen in practice? It is idiomatic to handle every error right after the function call that returns the error. To me, the lack of "if err != nil {" under the "bar(foo)" call really stings my eyes. I know, compile time checking is different from "it doesn't happen in practice". It's a tradeoff. I'm just wondering about the magnitude of the impact.
- randomdata 4y agoGo doesn't have errors, only values. Value handling could be improved, I'm sure, but anything that focuses on values that humans attach error meaning to is fundamentally flawed and will leave broken half-solutions. Each problem people say Go has with error handling is actually a more general problem with value handling and/or interfaces and the right solution would solve for those problems across the board. The problems are real, but not related to errors specifically.
- erik_seaberg 4y agoThere are always going to be cases where the happy path call tree should be abandoned because no useful value meeting requirements is going to be possible. What’s needed is to persuade Go devs to use panics instead of obscuring every function reinventing them by hand.
- randomdata 4y agoThe happy path should at least have a value that we oft categorize as error to branch on meaningfully. If the code has no purpose it wouldn’t need to be called. panic is certainly appropriate if programmer error left the application in an inconsistent state (an exceptional state, or exception for short as it is often referred to). I've never seen a Go developer, or developer in any other language for that matter, try to deal with these cases by hand.
- deleted 4y ago[deleted]
- marwatk 4y agoThe bigger issue is that returning the error without wrapping provides zero context and after a crash you're stuck trying to figure out which of a hundred calls to Write was responsible for "write error" or some other equally ambiguous message that's nearly impossible to track down. So you have to resort to a poor man's stack trace: if err != nil { return fmt.Errorf("my thing: %w", err) } (We use wrapcheck via golangci-lint to enforce it because not having context for an error has bitten us so many times)
- Gibbon1 4y agoThings I tend to do in my firmware for errors. Record the error line number where the error happened. Use __builtin_return_address(0) to fink on the caller. Idle thoughts. Despite it's provenience BASIC's on error goto has merit. The problem with try/catch is creating scoped blocks of code that's annoying. try { // scoped stuff } catch { } vs on_error_goto label; // stuff label: Also a thought is having a separate error_return keyword. And the ability to mark functions with 'no error'
- metaltyphoon 4y agoThe thing with try/catch is that many times, code that throws doesn't have to be handled on the call site! So you can easily have a happy path and kick the bucket to a location where the error can ACTUALLY be handled property.