3 ms·
> You're not supposed to return your custom error-like struct pointers from functions - you're supposed to return an "error" interface Per https://github.com/g
by the_gipsy 3y ago
> You're not supposed to return your custom error-like struct pointers from functions - you're supposed to return an "error" interface
Per https://github.com/golang/go/wiki/CodeReviewComments#interfaces https://github.com/golang/go/wiki/CodeReviewComments#interfa...:
Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values. The implementing package should return concrete (usually pointer or struct) types
There clearly is this one exception ONLY for the `error` interface. This advice plus the weird runtime check conventions like `Is` / `As` are nowhere to be seen for other interfaces in the ecosystem.
Go makes a lot of effort so that errors are special. There is an exceptional tuple-type that exists solely for returning errors, and the error-return-interface convention breaks interface-return conventions. If they implemented sum-types, then we would have the typical `Result<T, Error>`, and then go programs would have much more invariants and be much more composable. But presumably compilation- and/or run-time would be worse.
Another alternative would have been adding syntax sugar like rust's `?` for `if err != nil { return ..., err }`, and `match err { case CustomError: ... case error: ... case nil ... }`. And some helpers for composing/piping functions.
- rollcat 3y agoThere would be two major problems with sum types (especially for results) in Go 1.x: - There are genuine use cases where returning both a value and an error makes complete sense. My favourite example is doing I/O: some bytes were copied, but there was a problem with the rest. - It has significant overlap in scope & functionality with interface types. You could have "type Color enum { Named(string); RGB(int, int, int) }", or you could have "type Color interface { RGB() (int, int, int) }; type NamedColor string;" etc and it's not very clear which style you'd be supposed to use, and in what case. The first problem could be somewhat alleviated by having very special result types that can have both a return value and an error, but these would have to be different from regular "variant" results, so you'd end up either with two ways to do a very similar thing, or yet another layer of abstraction. The second problem is fundamental to the design of the language, and I would absolutely *hate* to see Go go through the same unholy mess as Python: "old-style" vs "new-style" classes, dataclasses (with third-party "attrs" as a stepping stone), protocols/generic containers, "match"/destructuring, all of these were clumsily grafted on and code that mixes all of these styles can be found all over the place. You do need to make ADTs and pattern matching a first-class citizen in 1.0, otherwise the place you end up in is not pretty at all. Go? Yeah, we have a lot of SortIntegers([]int) and SortStrings([]string) and SortByName([]Person) in older code, but that's about its biggest sin. As of 1.21, slices.Sort & slices.SortFunc are in std and fixing this older code is pretty much a mechanical task. It's not perfect, but at least it's not worse by trying too hard to be perfect.
- the_gipsy 3y ago> My favourite example is doing I/O: some bytes were copied, but there was a problem with the rest. How do you reconcile that with the general rule or convention that `if err != nil` the value is unsafe to use? Apart from this, I don't think sum-types should be added this late either. I should have written "if they had implemented". "old vs new style" does more damage than good. Today, it is what it is. What I do think is feasible is adding some more syntax sugar around errors.
- bheadmaster 3y ago> How do you reconcile that with the general rule or convention that `if err != nil` the value is unsafe to use? By reading the documentation: https://pkg.go.dev/io#Reader https://pkg.go.dev/io#Reader Conventions are not laws, and should not be followed blindly. > What I do think is feasible is adding some more syntax sugar around errors. The most common example I hear is something like Rust's `?` operator for propagating errors. In Go, errors should almost always be expanded with additional context, and programmers should think hard about errors and how to handle them. Syntax sugar would just make it easier for people to ignore errors. And you can't ignore errors forever - they always come up, most often in the place you least expect them.
- the_gipsy 3y ago> In Go, errors should almost always be expanded with additional context I'm convinced this is just a very poor way to cope with the lack of stacktraces.
- bheadmaster 3y agoActually, stack traces are a very poor way to provide context. I can't count how many times I've worked with some library in ${LANGUAGE_WITH_EXCEPTIONS}, and had an exception print out a giant stack trace, only for me to realize that the stack trace is useless because the place where exception is raised isn't the place where the error actually happened, but is actually some wrapper/worker collector/other kind of indirection mechanism. In other words, stack traces are directly bound to call stacks, and call stacks don't necessarily contain all relevant context of an error - they only do so in case of simple, single-threaded programs. Golang-style error-as-values actually provide real, human-curated context that is relevant to the operation at hand. They can be passed between goroutines, and are completely independent of any call stacks. That, in my opinion, makes them vastly superior to stack traces.