26 ms·
Banging errors in Go
- cratermoon 3y agoErrors are values. https://go.dev/blog/errors-are-values https://go.dev/blog/errors-are-values "Whatever you do, always check your errors!"
- uticus 3y agoAs an outsider (C#/Java) this has always intrigued me. I can only imagine what my opinion would be after using it for a few months with a team.
- all2 3y agoI've been pondering doing a short experiment where I abandon exceptions in C# and instead always return 1 or 2 values from my methods: maybe some output and an error object. I'm not sure how well this will work in practice. One issue that occurs to me immediately is that I'd be wrapping a lot of library functions in try/catch blocks just to push my error objects back up the call stack. I suppose the lazy option is to just ignore the libraries and only worry about my own code.
- bananapub 3y agoI predict you'll give up because: 1. it's very very tedious to have two unrelated error systems in one language (yours and the one everyone else in C# uses) 1. as Go demonstrates, this type of error handling is extremely tedious even by itself
- all2 3y ago> this type of error handling is extremely tedious even by itself But is the tedium worth it?
- jakewins 3y agoI’ve done this at times in Python and C#, I really really miss Go or - better - Rust style errors there. But, exactly like you say, you end up fighting libraries - particularly the standard libraries, and it ain’t a fight you will win. In typescript it actually kind of works - better at least. JS APIs often are errors-as-values already because of the old continuation async APIs, and typescript of course lends itself incredibly well to rich return types.
- metaltyphoon 3y agoC# doesn’t really offer language syntax to deal with self many DUs. So you will probably suffer. Perhaps it can change if the language supports DU in the future.
- uticus 3y agoOne of the things that intrigues me about Go is that it is not meant to be used in isolation. So, the experiment you list here is actually a whole different experiment if done in isolation (on like say a hobby project maintained only by yourself) versus something actively maintained by a whole group of people. The experiment really should be done with some peers - if you can find willing victims (I mean, volunteers)! To me part of a language's utility is measured by how well it works in the group setting. That's what typically drives the industry, and more to the point for me it is the situation I will encounter most of the time.
- umvi 3y agoIt's an intentional design decision of go. In C#/Java, exceptions are often (ab)used to create invisible control flows through the program that are extremely difficult to follow since exceptions can float up arbitrarily large stacks before being intercepted and handled. In go since you are forced to handle every error on the current level of the stack you are on, there are no invisible control flows. Either you pass the error one level up the stack, or you don't.
- ubertaco 3y ago>In C#/Java, exceptions are often (ab)used to create invisible control flows through the program that are extremely difficult to follow I hear this often from Go advocates, but as someone who cut my teeth in Java in 2004, I've only ever seen this done in one codebase: an old streaming parser that I haven't seen since, and where the alternatives were, at the time, generally _more_ confusing than tossing an "unexpected end of input" exception that contained context. In the 19 years since (14 of which have been my professional career, mostly with Java as part of the job _somewhere_), I've never seen it since. In the meantime, though, I've run into better, more-explicit error-handling strategies (monadic errors, union types with explicit unwrapping) that manage to make the usually-bad option ("I'm swallowing this error") explicit and intentional, which is not something Go manages to achieve. (Interestingly, these better approaches all pre-date Go, so it's not like there wasn't a better state of the art to learn from.) But Go also doesn't make error-propagation easy either. Somehow, Go manages to optimize for accidentally swallowing errors, which is sort of impressively bad. It reminds me of something like INTERCAL: engineered to be as much a footgun as possible. INTERCAL is a parody, though, so it has that going for it.
- slantedview 3y ago> Somehow, Go manages to optimize for accidentally swallowing errors How so?
- josephcsible 3y agoBecause if you forget to write the code to account for an error, your program still compiles (unlike languages that use sum types for errors) and then said error will be accidentally swallowed when it occurs (unlike languages that use exceptions for errors). The only other languages I can think of that are as bad as Go about this are C and assembly.
- masklinn 3y ago> Whatever you do, always check your errors! Would probably help if you did not need an external linter to remind you. Alas, here as well Go is all hat.
- Animats 3y ago"All hat and no cattle", a Texas insult?
- cratermoon 3y ago> if you did not need an external linter to remind you That's true if the function doesn't return a value (other than the error) or if it does and the caller also ignores the return value. That is, given func F() error { ... } It's legal to call F() without checking the return. However in the case that the function returns a value and an error, like so func G() (int, error) { ... } then it's fine to just call G() and ignore anything returned, but i := G() will cause a compile error. It's possible to assign one or both values to the Blank Identifier, the underscore _, so ignoring the error, while possible, requires the code to reflect the intent, like so i, _ := G() but i, err := G() will fail to compile if either i or err are not used following the call. https://go.dev/play/p/Z8xNWiJHPV0 https://go.dev/play/p/Z8xNWiJHPV0 Maybe the compiler should require the error return to be assigned for F(), that's a bit of a quirk that's under discussion.
- masklinn 3y ago> That's true if the function doesn't return a value (other than the error) or if it does and the caller also ignores the return value. “Yes” would have done just fine, especially as this is not uncommon when it comes to IO. But then again who’d do IO in Go right? > will fail to compile if either i or err are not used following the call. a, err := G() if err != nil { panic(err) } i, err := G() i, err = G() err = F() fmt.Println("Got", a, i) > Maybe the compiler should require the error return to be assigned for F(), that's a bit of a quirk that's under discussion. It’s not “a bit of a quirk”. The langage only checks for unused variables which is woefully insufficient and unfit for purpose, and has been since the langage was first released.
- bananapub 3y agoyes, ted's code automates checking your errors, which is something Go itself neglected to do or even remind you the programmer to do
- badrequest 3y agoIt also prevents adding relevant context to the errors, meaning you get all the joy of hunting through call stacks to figure out what went wrong in a sufficiently complex program.
- t0astbread 3y agoWhen I write tooling for myself in Go, I usually wrap all of my errors with stacktraces since I hate having to grep for a string that may be interpolated or duplicated throughout the codebase. Addendum: It is still a good thing to be able to add context to the error message though, so I think you've got a point.
- HL33tibCe7 3y agoPart of me agrees with you. But part of me also sees the Go's team point on this, which is that not all functions always need their error checked - as an obvious example, fmt.Println. Sure, the compiler could add explicit exceptions for those cases, but that's a very unclean solution and it doesn't handle third party libraries. The "Go way" is to use the errcheck tool to do that. And on balance I think that having that defined in a separate tool - which can be configured by the user to exclude modules of their choice, and comes with good defaults - is the correct choice.
- TwentyPosts 3y ago>not all functions always need their error checked - as an obvious example, fmt.Println. I agree, but a lot of other languages handle this much better. In Rust (predictably!) you receive a Result type, and if you don't want to check the error you either .unwrap() it or apply the ? operator to pass it up. I feel like making "let's not check this error" explicit rather than implicit would be an improvement. Currently in Go it's impossible to tell whether someone forgot to check an error, or if they omitted the check intentionally. As an aside: I feel like if you are ever in a situation in which fmt.Println returns an error, then whatever situation you are in is already far, far beyond saving. Maybe fmt.Println should just panic on an error, that seems better than output silently being dropped!
- ttoinou 3y agoWe can’t fix the people who cherish their imperfections as a sign of humanity Ouch. Spoken as a wise man
- demi56 3y agoDepends on the person who sees such as imperfections, not every perfection is the right one. The wise take should be “is it the right one”
- HL33tibCe7 3y agoThis doesn't allow you to wrap errors, which is essential for writing idiomatic Go
- Xeoncross 3y agoAgree, wrapping errors continues to build context until you have a clear trail of blame for the issue. Most languages just expect you to read the stack trace and figure it out. Proper error handling tells you explicitly how it failed at each level so you can decide what to do at each level of the codebase. Having global try/catch (or bang as the author suggests) is easier at first, but makes actually handling error paths worse.
- Xeoncross 3y agoHere is an fictitious example of an error that was wrapped from deep in the internals of the net package all the way up to the HTTP handler serving the user. It was wrapped five times and clearly outlines what went wrong at each stage. > failed loading config: unable to reach host example.com: tcp: dns lookup: timeout https://pkg.go.dev/errors#Is https://pkg.go.dev/errors#Is and friends can be used to figure out what type/class of error exists in this chain so there is no loss of information either. It's more than just string concatenation. It's an actual tree of errors that also presents well for the logger.
- Cthulhu_ 3y agoAnd the stack trace comes with its own issue; they are expensive. Which is fine, if the exception is actually true to the name - an exceptional situation. But in practice / production, exceptions / errors are not exceptional. Take a HTTP server. Due to a myriad of reasons, the connection between the server and a client can be severed, triggering an error because the connection was broken. Is that exceptional? No, the internet is unstable and clients are unreliable. Is it therefore valuable to generate a stack trace every time that happens? One exception is fine, but if you go web scale like Google, the "this connection was interrupted" case happens millions of times a day. Millions of times the cost of generating an exception + stack trace becomes really expensive. An error is cheap in comparison.
- 3y ago
- candiddevmike 3y agoFor me personally, any kind of codegen solutions are off the table unless the library/ecosystem demands it, like k8s :(. You lose so much productivity when dealing with codegen: linting, LSPs, testing, hell even just understanding WTF this does.
- jmprspret 3y agowhat's wrong with zd, _ := gzip.NewReader() ? This too is obvious to go readers, and the current correct way to ignore errors.
- goalonetwo 3y agoThis completely ignores the error. What you want is to send the error back up, without the need to be super verbose with if err != nil { return nil, err }
- jmprspret 3y agoOkay, I see where my misunderstanding is now. I didn't realise this is for sending them "up".
- masklinn 3y agoThe goal is not to ignore the error but to make it easier to propagate it. Similar to Haskell’s do form or rust’s ? operator.
- wolf550e 3y agoHe doesn't want to ignore the error, he wants exceptions-like behavior where the exceptions just propagate up by themselves if you don't catch them inside the function. Like Java or Python exceptions.
- morelisp 3y agoAlso a good example of programmer masochism, there's absolutely no good reason for zd, err := gzip.NewReader(fd) to return an error rather than waiting for an actual read. The stdlib is usually pretty good about this but third-party libraries are rife with this kind of misdesign.
- imran-iq 3y agoYou should try reading the source code and you will get your answer. NewReader[0] calls Reset[1], which calls readHeader[2], which the name should tell you what its doing. --- 0: https://github.com/golang/go/blob/go1.21.3/src/compress/gzip/gunzip.go#L94 https://github.com/golang/go/blob/go1.21.3/src/compress/gzip... 1: https://github.com/golang/go/blob/go1.21.3/src/compress/gzip/gunzip.go#L113 https://github.com/golang/go/blob/go1.21.3/src/compress/gzip... 2: https://github.com/golang/go/blob/go1.21.3/src/compress/gzip/gunzip.go#L174 https://github.com/golang/go/blob/go1.21.3/src/compress/gzip...
- morelisp 3y ago"My answer"? I didn't ask a question! I know what the code does, that doesn't make it less bad design.
- phlakaton 3y agoThe hoops you have to jump through when you don't have a macro system...
- linux2647 3y agoBut that’s part of the value proposition of Go: that it’s a small(-ish) language. I like that it lacks magic found in many other languages. Though I concede that the “simplicity” sometimes comes at the expense of developer ergonomics
- phlakaton 3y agoYet here we have an example of bad developer ergonomics that so frustrates one developer that they are willing to use whatever bonkers syntax they can get the parser to choke down, and build an AST transformer for Go, to try to get something remotely approximating sane for a single common case. Which is saying nothing of Go libraries heavily relying on comment-based programming (err, sorry, we call them "struct tags") to generate boilerplate. The language is successful, but "expense of developer ergonomics" is an understatement.
- skybrian 3y agoThe AST fix is trivial. The key is getting consensus to do it. Without consensus you get a dozen different macros that do more-or-less the same thing. It seems like a language change would be good; it’s just taking longer than it should.
- qaq 3y agoOn the upside there are fewer hoops to jump through when you are reading the code.
- the_gipsy 3y agoThis is something that should be in the language, not some preprocessor. Like rust's ? operator.
- Animats 3y agoYes. Rust struggled with that for about three rounds of verbose error handling, until finally settling on "Result<useful, Error>" and "?". That seems to be about right. C++ exceptions are too much. Writing it all out as in C and Go is too little. The Rust solution is a good midpoint.
- masklinn 3y agoUh? Try! was added in 0.11, and the feature found to work well enough and be sufficiently valuable that ? superseded it in 0.13.
- debugnik 3y agotry! was added in 0.10 (2014), and ? was stabilized in 1.13 (2020), according to my quick check on GitHub releases. Note that the underlying traits to implement ? on custom types are still unstable.
- chrismorgan 3y agoCorrection: 1.13.0 was not released in 2020, but on 2016-11-10. The rest of the comment is accurate.
- debugnik 3y agoOh, noted. I guess Rust uploaded old releases to GitHub at some point during 2020, then.
- chrismorgan 3y agohttps://github.com/rust-lang/rust/blob/master/RELEASES.md https://github.com/rust-lang/rust/blob/master/RELEASES.md is an excellent file for finding all of this kind of stuff.
- mtlynch 3y agoI feel like I missed a few steps here. Is the author just telling us about a tool they privately made but are not sharing? And any code you write with this "bango" operator is incompatible in anyone else's Go environment unless they have the author's same tool configured to preprocess their code?
- t0astbread 3y agoGiven that the repo says "Just some little toys." in the description[1], I don't think this is made to be used in production. Otherwise it would be an interesting idea but also more or less what Lombok is for Java. [1] https://humungus.tedunangst.com/r/fungo https://humungus.tedunangst.com/r/fungo
- deagle50 3y agoAssigning errors at the function call... Worst part of go. Just add try/catch already.
- sethammons 3y agoit is my second or third favorite part of Go. What sucks more than just about anything is exceptions as control flow. It is a spooky GOTO at a distance. To everyone who complains about the "if err != nil { return err }", honestly, you are doing it wrong or you have a toy application. I have written several, large, high scale, highly available systems processing multiple billions of requests a day. When analyizing those code bases, empty error returns like that accounted for 4% or less of our code. We always were doing _something_ with the error. Metrics, logging, reties, sending off to a different workstream, etc.
- deagle50 3y agoIt's not just the empty return. It's that every fallible function call starts with `err = foo()` or `value, err = foo()`. You're forced to read `err` at the beginning of every function call even if you don't care. How will sugar for an empty return or appending an error handling scope harm anything?
- deagle50 3y agoAnd I understand the advantages over exceptions, but we've made some nice progress since the mid 2000s. Sum types and error unions aren't rocket science and even the assembly line programming model that Go seems to have built to enable could adopt them up in a day. You could even have `catch` capture only the last value in the result tuple and it would still be big improvement.
- sethammons 3y agosum types and pattern matching are great. For me, I'm anti-exception. "Point on the doll where the exceptions hurt you." I worked on a project with exceptions as control flow (cough, twisted python, cough), and the error handling was caught several classes and mixins up and over in the file directory. In some far away file, your exception triggered a callback or an errback, and if that excepted, similar magic happened. It got to the point that several engineering choices had to be made around "well, this really would be nice to have an exception and some standard handling, but the framework will take it and do strange things." I _love_ handling my errors where they are created.
- frankjr 3y agoFun fact: There are so many proposals aimed to tackle error handling ergonomics that it has its own label in the bug tracker. https://github.com/golang/go/issues?q=+is%3Aissue+label%3Aerror-handling https://github.com/golang/go/issues?q=+is%3Aissue+label%3Aer...
- liampulles 3y agoI'm sure I'm in a very tiny minority, but I kinda like having errors as values - I prefer having errors be things that are part of the normal control flow of my code, and that can be handled explicitly within an if block (which may start simple but can and does evolve to wrap details, log and skip, fallback to an alternative, etc.) Yes it means errors shows up everywhere in your code - the same types of errors are something all languages have to deal with, just accept our faulty reality for what it is and have the discipline to account for it.
- xyzzy_plugh 3y agoI'd be surprised if you're in a minority -- maybe here that's true but most Go developers I know and work with are pretty content with the state of affairs regarding error handling.
- morelisp 3y agoMost Java developers I know and work with are pretty content with the state of affairs regarding error handling. But it sucks too.
- threeseed 3y agoJava is significantly better than Go. You have the option to explicitly handle every error, not handle errors for certain methods, or bubble up errors to a single error handler or any combination thereof.
- yellowviking 3y agoRust, FTW
- omginternets 3y agoYet another language-ergonomics post that confuses essential complexity (the function is doing complex business logic that happens to be necessary) with accidental complexity (in this case: having to type more). With this distinction in mind, the entire article boils down to two points: 1. The author is basically complaining about having to type more (fair, but not interesting) 2. The author proposes that appending a "!" to a statement is somehow clearer than explicitly returning an error value (absurd and eyebrow-raising) The whole article then becomes "I like Rust's syntax better".
- ColonelPhantom 3y agoHaving a common idiom for common operations like error propagation is not "absurd and eyebrow-raising". What sounds absurd to me is obscuring the actually interesting control flow significantly in favor of either showing the trivial control flow or alleviating the need to learn the one (1) single operator that represents said trivial control flow.
- omginternets 3y agoYes, but we do have a common idiom for error-propagation in Go. It's more explicit than the proposed alternative, in fact.
- lmm 3y agoYou don't have an idiom, you write it out longhand every time, which ends up obscuring the important control flow and business logic under a pile of ceremonial repetition.
- omginternets 3y ago"if err != nil {...}" is the idiom, my friend.
- cbarrick 3y agoThe author has reinvented the ?-operator from Rust and Ruby, but for Go. Here's the same function in Rust: fn decomp(filename: &Path) -> Result<Vec<u8>, io::Error> { let fd = File::open(filename)?; // File is automatically closed by its destructor. let zd = GzDecoder::new(fd); // flate2::read::GzDecoder::new does not return an error. let mut data = Vec::new(); // Rust makes the caller allocate the buffer for reads. zd.read_to_end(&mut data)?; Ok(data) } I think this is great. From a reader's perspective, it can dramatically improve readability in a lot of functions. From a writer's perspective, these kind of solutions make it easier to compose expressions without interleaving if-statements after every other line. Yes, sometimes you want to add context to your errors instead of using this syntax. But you can always use verbose syntax when it's needed, and terse syntax when it's not.
- zakm 3y agoThe only argument I can think of against this would be that it would maybe slightly discourage adding the verbose details and would generally decrease the quality of error messages slightly. That said, even so it's probably worth the trade.
- slantedview 3y agoI agree, especially if you can _choose_ when you want to be a bit more verbose and add more detail, or not.
- shepmaster 3y agoRust libraries like my [SNAFU] allow you to both add additional details and use the `?` operator: read_to_string(path).context(ConfigFileSnafu { path })?; [SNAFU]: https://docs.rs/snafu/latest/snafu/ https://docs.rs/snafu/latest/snafu/
- bibabaloo 3y agoErrors are composable so this isn't such a problem in practice. Most of the prod code I wrote would do something like this use thiserror::Error; #[derive(Error, Debug)] enum Error { #[error("Could not open given decomp file: {0}")] FileOpen(#[from] std:io::Error), #[error("Compressed read error: {0}")] CompressedRead(#[from] gz::Error) } fn decomp(filename: &Path) -> Result<Vec<u8>, Error> { let fd = File::open(filename)?; // File is automatically closed by its destructor. let zd = GzDecoder::new(fd); // flate2::read::GzDecoder::new does not return an error. let mut data = Vec::new(); // Rust makes the caller allocate the buffer for reads. zd.read_to_end(&mut data)?; Ok(data) }
- solid_fuel 3y agoI've worked heavily with Go for several years now but recently I have been transitioning to Elixir. In my opinion, Elixir handles this kind of error return problem better with two separate patterns: 1) Somewhat similar to what the author of this post achieved, there is a pattern commonly used in Elixir libraries where there are often variants of library functions which end with ! which raise an exception when a problem is encountered. For example, the file module https://hexdocs.pm/elixir/1.13/File.html#read/1 https://hexdocs.pm/elixir/1.13/File.html#read/1 provides multiple ways to read a file: File.Read() -> returns {:ok, <<content>>} or {:error, <<reason>>} File.Read!() -> returns <<content>> or raises an exception 2) Pattern matching is also a common way to deal with this. # This will raise a pattern matching exception if something other than :ok is returned from File.Read {:ok, content} = File.Read(...) Pattern matching like this isn't supported in Golang but I think it would be fantastic to be able to write zd, nil := gzip.NewReader() and have that panic if an error was returned instead.
- sethammons 3y agoI like a lot about elixir, including what you point out. Pattern matching is awesome. Its type hinting via spec is not enough (i hear some developments are on their way here). I don't like that I often had to jump up several callers to understand the arguments coming into my function. Go wins here. And I also require performance. While it may have been query abuse by Ecto, the Elixir code base I was in was only able to handle like 3k rps across 5 nodes. I expect nearly 2x that from a single similarly sized Go node making Go 10x more performant in naive implementations. Easier to read and more performant? I chose Go. It also plays nicer with K8s; we had trouble getting nodes linked up in elixir to allow multi node BEAM features.
- wesleyd 3y agoI like to joke that the most important question in Irish architecture is: where does the water go? Similarly I love how in go error plumbing is front and center, at least as important as data plumbing, usually more important. For some problems, one wants to gloss over things that might go wrong. Don’t use go for those. Go is for when how something fails is more important than how it works.
- marcus_holmes 3y ago> New Code is a lot cleaner: matcher2 := match.If(match.Binary(match.Ident("err"), token.NEQ,match.Ident("nil")),match.Block(match.Multi(match.Return(match.Multi(match.Ident("nil"), match.Ident("err")))))) In what world is this cleaner or easier to read?
- slantedview 3y agoThat's not the code that a user would write to use this pattern, that's code that a library that implements this pattern might use.
- marcus_holmes 3y agoI mean, you're not wrong. But how does that change anything? I still have to read it, and it's still harder to read as one long chain.
- hmry 3y agoI don't get it. Why do you have to read the internal implementation details of a preprocessing tool when using it?
- marcus_holmes 3y agoBecause it modifies code you're going to deploy on a production server. Therefore you need to audit it to make sure it's not going to deploy malware.
- grose 3y agoI love weird little experiments like this! I was in favor of the check-handle proposal[1] that landed at the same time as the generics proposal and a little sad that it seems to be abandoned. Bikeshed: How about _two_ bangs? data := !!io.ReadAll(zd) The double-bang pattern is unused in Go because it doesn't coerce non-booleans to booleans, so you can appropriate it without stepping on anyone's toes :) The main disadvantage of a Rust-?-like syntax for Go is you lose the ability to add context to the error (e.g. wrapping with `fmt.Errorf("context goes here: %w", err)`). Some people prefer to shove a stack trace inside the error to work around this. I'm not sure what the perfect solution would be. The check-handle proposal adds a special handle keyword to deal with it. BTW, I am aware of the 'errors are values' concept -- I was the guy interpreting for Rob Pike and the nice Japanese fellow who asked him about it at a conference afterparty and inspired the blog post. I think this pattern is great but it's quite hard to distill it into generic advice ("delay reporting errors until the last possible moment"?). Designing ergonomic APIs for Go can be challenging, and purely anecdotally a lot of the proprietary Go code I've seen at various companies does not do a great job at it. My #1 wish for Go is that one day Go will figure out sum types and pattern matching. I know that the way interfaces work make this challenging, but I have hope. Using sum types to handle errors makes it much more obvious what the 'right thing' to do is. [1]: https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md https://go.googlesource.com/proposal/+/master/design/go2draf...
- josephcsible 3y ago> The main disadvantage of a Rust-?-like syntax for Go is you lose the ability to add context to the error You don't lose the ability to just by that syntax existing. You could still do so by writing it out the long way like you have to do anyway today in Go.
- deleted 3y ago[deleted]
- grose 3y agoIndeed. What I meant is that compared to the pre-existing error handling, there are use cases it won’t fit. I am pro-? syntax. I think it would be useful even without being able to add context, but I’m also interested to see if there are solutions that could handle this for you.
- slantedview 3y agoI love these kind of experiments. Before Java had lambda expressions, there were a few creative libraries that introduced them. It wasn't perfect, but it helped drive towards good use cases for that language feature before it made it into the language officially.
- fsociety 3y agoPeople are overly dramatic when it comes to typing err != nil. I’m not going to say it’s a holy grail in error checking. But I have yet to be convinced on the benefit of exceptions vs. the hell hole of complexity they bring. They stuck to their guns, took what C did, and made it 1000x better. And in the same sweep made a language that is dead simple to read and code review.
- lamontcg 3y agoThe alternative isn't exceptions. Neither this idea or rust's '?' operator are exceptions.
- josephcsible 3y agoIt's not that it's hard to type. It's that in Go, if you make a mistake with your error checking, your program has a bug. In better languages like Rust and Haskell, if you make the same kind of mistake with error checking, your program doesn't compile. And Go isn't even close to 1000x better than C. It makes almost all of the same mistakes that C did (especially the billion dollar mistake), despite being new enough that it should have learned from them.
- danenania 3y agoI like both Haskell and Rust (in theory; haven't written anything nontrivial in either). But they both seem to encourage you to spend a lot of time and energy on the meta aspects of programming. You can build safe, beautiful systems, but you can also happily spend all day (all week? all month?) playing various forms of code golf without actually producing anything. Go, like C, is a very get-it-done language. It doesn't let you have any fun at all with abstractions, so you end up just doing your work instead. In spite of its warts, I think Go is remarkable and unique for this quality.
- HippoBaro 3y agoThere's absolutely nothing in Rust that mandate error handling. You can always ignore them, just like in any other language.
- demi56 3y agoSome of these proposals don’t work especially for Go, Go type system is so plain infact it’s too plain that allowing for “special compiler keywords” can do more harm than good, example is “?” What if there’re 4 return values?, in Go where Order is pretty important, to Go this alone increases compile time
- gorgoiler 3y agoRegular go style is to return (value, error) tuples from most functions which can have runtime errors. This raises a bunch of questions which, thanks to some of the comments in this thread, I hadn’t really thought about until now. I think it is implied that one should not return both a value and an error. Is this true? Is there much code in go that returns both and lets you decide if you want to take the value and carry on, or take the error and stop? Is there much idiomatic code where a function returns a tuple of actual values? For example a function in some shipping code that returns the largest dimension of a package to be shipped where it can only be of a maximum size or a maximum weight? Does this kind of idiomatic code also rely on the contract that only one value will be set? Does go have the typing facility to have a union type for both a value and an error, and does anyone use this in preference of returning tuples? If types in go can be null then this still allows for ambiguity and it would surely be unpopular to produce code that breaks the value/error tuple pattern, but I assume some left field hackers are breaking the rules somewhere.
- aldanor 3y agoThat's called 'sum types' and that's what Go lacks. See some bikeshedding and ongoing discussion [here](https://github.com/golang/go/issues/57644 https://github.com/golang/go/issues/57644).
- hknmtt 3y agothe only people who constantly complain about errors in Go are those who have no or only small experience with long running programs, so mostly scripts(php, python,...).
- gatestone 3y agoIf you browse random Go code from https://pkg.go.dev/ https://pkg.go.dev/ you will have a hard time finding that hypothetical case where most of your code is error handling chores. The problem is artificial.
- otabdeveloper4 3y agoYes, I agree. If you just ignore errors, they will go away. #yolo P.S. That said, any program written in Go is an absolute shitshow of crappy UX because of the (inevitably) inconsistent and often incorrect error handling.
- rollulus 3y agoWith a statement like that in your postscript I think it would be fair to elaborate.
- otabdeveloper4 3y agoOnce a Go program goes off the happy path, the only way to figure out what happened is to read the source code. (No, you cannot have a stack trace. Stack traces make junior programmers feel uncomfortable, and so Google made them verboten.) And even then, because Go uses insane and outdated OOP practices like casting all pointers to a generic base class, even reading the source code is a exercise in frustration and rage.
- rollulus 3y agoThere’s error wrapping for adding context. Where one adds a human readable context to errors. Stack traces as I know them from the JVM world, with their line numbers and method names, seem a lot more coupled to the source code, so I’d say it’s the other way around really.
- otabdeveloper4 3y agoI speak as a user of programs written in Go. (By junior programmers who love ""simple"" programming languages.) Thank God I don't have to code anything in Go myself.
- c7DJTLrn 3y agoPlease no. Go is designed to be easy to read and easy to write. err != nil is not hard to read. If you don't like the repetition, it simply isn't the language for you. I'm perfectly happy with how Go does it. I do not want the language morphing into another Rust.
- aldanor 3y agoArguably, "?" Is not hard to read either.
- cabirum 3y agoI wonder if Go syntax could be extended to tag statements in addition to struct fields: func decomp(filename string) ([]byte, error) { fd, err := os.Open(filename) `err` defer fd.Close() zd, err := gzip.NewReader(fd) `err` data, err := io.ReadAll(zd) `errw:"your device could be broken: %w"` return data, nil } Where `err` tag just forwards error if nonnil, and errw is similar to Errorf.
- gatestone 3y agoDesigning better error handling in Go is an ongoing work. Proposols are being considered. Personally I don't see a big problem in real life Go code bases. Note that there is kind of a philosophical cul-de-sac here. If you understand and expect your "errors" (= exceptional situations that prevent the program to do what we thought the user wanted) you can handle them, and they become part of your program normal logic. If you don't understand or ainticipate your errors, or choose to neglect situations that are not really rare or are really dangerous, no amount of special language constructs will save your users. https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md https://go.googlesource.com/proposal/+/master/design/go2draf...
- Cthulhu_ 3y ago> = exceptional situations that prevent the program to do what we thought the user wanted Just to latch onto this remark, are they actually exceptional? Say you have a REST API, does something with a database. 1 in 1000 requests fails, so it's exceptional I suppose. But then you go web scale, and your application gets called a million times per minute. Suddenly you're dealing with 1000 errors per minute; not so exceptional anymore. Besides, errors can also be things like 'SQL row not found', which are expected in the normal run of things.
- JonChesterfield 3y agoSum types are the right thing for a statically typed language. Returning an error and a value as a product type with an implicit promise to ignore the value part if the error part has a specific value is not the right thing. There's a credible chance sum types are the right thing in a dynamically typed language too but I haven't settled on exactly what what should look like yet. It's not a thing in the dynamic languages I know of.
- knodi 3y agoI like this error handling of go. It give a great layer for defensive programming. You have to make a conscious decision to ignore the error.
- mortallywounded 3y agoI like having errors and handling them explicitly, though it's a very common pattern to do something, and if there's an error, bubble the error up. I think instead of a bang it would be better to have a keyword like this.. similar to defer to defer a function. Maybe `try` to give a function a try and if any error is returned then bubble up. defer cleanup() bar := try somethingelse() // equal to bar, err := somethingelse() .. return err
- ansiwen 3y agoI will promote my shorterr package, that I just released a few days ago, and that tries to implement rusts ? operator as much as possible: https://pkg.go.dev/github.com/ansiwen/shorterr https://pkg.go.dev/github.com/ansiwen/shorterr
- foderking 3y agogo error handling is retarded. monads exist
- sediment7542 3y agoThis isn’t really an improvement on code readability. Let it be explicit and clear. No magic symbols that shortcut things.
- thomas-guettler 3y agoI had almost the same idea some days ago. Just syntax which I like more: ``` fd, .. := os.Open(filename) ``