5 ms·
I'm not sure why you'd use a class like this in Go when you have multiple returns and an error interface that already handles this exact use case.
by lenish 6y ago
I'm not sure why you'd use a class like this in Go when you have multiple returns and an error interface that already handles this exact use case.
- erik_seaberg 6y agoIt's a lot cleaner to pass a Result<T> through a channel or a slice than to create two channels or slices and confirm everyone's following the same convention when using them.
- lenish 6y agoI concede that there are probably scenarios where this design makes sense within that context. I typically find that either I care about a single error and terminating the computation, or I don't care about errors at all. In the former case, the primitives in the sync package (or just an error channel which we send to once and close) are adequate. The latter case presents no issues, of course. At $work we definitely have examples where we care about preserving errors, and if that tool were implemented in Go a solution like a Result struct containing an error instance and a data type instance could make sense.
- apta 6y agoBecause multiple return values for handling errors is a strictly inferior and error prone way for dealing with the matter.
- californical 6y agoIt's interesting that you say this, because I've had the opposite experience. I wouldn't say it's strictly inferior, because there are definitely upsides. If it was strictly inferior, why would a modern language be designed that way -- there must be some debate right? I love multiple returns/errors. I find that I never mistakenly forget to handle an error when the program won't compile because I forgot about the second return value. I don't use go at work though, I use a language with lots of throw'ing exceptions, and I regularly miss handling exceptions that are hidden in dependencies. This isn't the end of the world in our case, but I prefer to be more explicit.
- apta 6y ago> If it was strictly inferior, why would a modern language be designed that way golang is not a modern language (how old it is is irrelevent), and the people who designed it did not have a proper language design background (their other accomplishments are a different matter). Having worked on larger golang code bases, and I've seen several times where errors are either ignored or overwritten accidentally. It's just bad language design.
- fooster 6y agoError handling is hard, period. Error handling in go is no worse than any other language, and in most ways it is better being explicit and non-magic. > people who designed it did not have a proper language design background Irrelevant. > It's just bad language design. try { ... } catch(Exception ex) { ... }
- apta 6y ago> try { ... } catch(Exception ex) { ... } The error here is explicitly handled, and cannot be accidentally ignored. Unlike golang where it's quite easy for errors to go ignored accidentally.
- hactually 6y agobecause it has try/catch. Without that (which would be similar to not checking the err in go) it explodes or throws to a layer up that may not expect it. Each language has its wonks.
- apta 6y ago> Without that (which would be similar to not checking the err in go) it explodes or throws to a layer up that may not expect it. It's not similar to that at all. Without it, the exception bubbles up until it gets caught somewhere, or crashes the program with a useful stacktrace. With golang, it just goes undetected, and the code keeps running with corrupt state, without anyone knowing any better.
- lenish 6y agofunc foo() (*SomeType, error) { ... return someErr } ... result, err := foo() if err != nil { // handle err } // handle result vs type Result struct { Err error Data SomeType } func (r *Result) HasError() bool { return r.Err != nil } func bar() *Result { ... return &Result { ... } } ... result := bar() if result.HasError() { // handle result.Err } // handle result I'm not really sure I see the benefit to the latter. In a language with special operators and built-in types it may be easier (e.g. foo()?.bar()?.commit()), but without these language features I don't see how the Result<T> approach is better.
- et1337 6y agoGo can't really express the Result<T> approach. In Go, it's up to you to remember to check result.HasError(), just like it's up to you to check if err != nil. If you forget that check, you'll try to access the Data and get a nil pointer exception. The Result<T> approach prevents you from accessing Data if you haven't handled the error, and it does so with a compile-time error. Even with Go's draconian unused variable rules, I and my colleagues have been burned more than once by forgotten error checks.
- jatone 6y agothere are linters that will help you with that. https://github.com/kisielk/errcheck https://github.com/kisielk/errcheck https://golangci-lint.run/usage/linters/ https://golangci-lint.run/usage/linters/ has a solid set of options.
- nimish 6y agoI just wish the linter was integrated into the compiler. And that code that didn't check would simply not compile
- apta 6y agoThe alternative is not the Result type you defined, but something along the lines of what languages like Rust or Haskell define: https://doc.rust-lang.org/std/result/ https://doc.rust-lang.org/std/result/
- dgb23 6y agoI would say it is a very ergonomic way of doing this. It allows for writing in a more exploratory way until you know what your error handling story is. Then, even if you choose to propagate it later, you just add it to your signature. Also it is very easy to grok and clear. Definitely not strictly inferior.