4 ms·
Sorry, I wasnt specific in that part. When I say 'rust style' Im really just referring to a union type of `result | error`, with a way to check the state (eg is
by JamesSwift 1y ago
Sorry, I wasnt specific in that part. When I say 'rust style' Im really just referring to a union type of `result | error`, with a way to check the state (eg isError and isResult) along with a way to get the state (eg getResult and getError). Optionally '?' and '!' as sugar.
That said, the other responder points out why the sum type approach is not favored (which is news to me, since like I said I havent followed the discussion)
- 9rx 1y agoIn Go, values are to be always useful, so `result | error` would be logically incorrect. `(result, result | error)`, perhaps – assuming Go had sum types, but that's a bit strange to pass the result twice. Just more of the pitfalls of it not being clear how Rust-style applies to an entirely different language with an entirely different view of the world.
- JamesSwift 1y agoWhat is useful about the value of `x` in the following code? x, err := strconv.Atoi("this is invalid") On the contrary, `x` is _lying_ to you about being useful and you have absolutely no idea if the string was "0" or "not zero"
- deleted 1y ago[deleted]
- 9rx 1y ago> and you have absolutely no idea if the string was "0" or "not zero" You do – err will tell you. But in practice, how often do you really care? As Go prescribes "Make the zero value useful" your code will be written in such a way that "0" is what you'll end up using downstream anyway, so most of the time it makes no difference. When it does, err is there to use. That might not make sense in other languages, but you must remember that they are other languages that see the world differently. Languages are about more than syntax – they encompass a whole way of thinking about programs.
- kbolino 1y agoI think it's worth noting that, while the general consensus has converged around (T, error) meaning T XOR error, it does not necessarily mean that. There are some places that violate this assumption, like the io.Reader and io.Writer interfaces. Especially io.Reader, where you can have (n>0, io.EOF), which also isn't even a proper error condition! (This isn't a big problem, though, since you rarely need to directly call Read or Write).
- deleted 1y ago[deleted]
- kiitos 1y agoIf a function `func foo() (int, error)` returns a non-nil error, then the corresponding `int` is absolutely invalid and should never be evaluated by the caller, unless docs explicitly say otherwise.
- XorNot 1y agoAnd this is why I prefer exceptions. Errors are common but they are errors: they absolutely represent an exceptional branch of your control flow every time. It seems reasonable to ask if that int should even be available in the control flow syntactically.
- kiitos 1y agoErrors are just values, same as other values, it's in no way "exceptional" for a caller to get an error back from a call to some other code. If a function can fail it needs to return an error, if a function call fails the caller needs to deal with that error, not difficult stuff here. "Happy path" is no more or less important than "sad path" and both should be equally represented in the source code as written.
- XorNot 1y agoThat's doctrine. Saying it doesn't make it useful. A program serves a business need: so it's well recognized that there's a distinction between business logic, and then implementation details. So there's obviously no such thing as "just an error" from that alone: because "a thing failed because we ran out of disk space" is very different to "X is not valid because pre-1984 dated titles are not covered under post-2005 entitlement law". All elephants have 4 legs, but not all things with 4 legs are elephants, and a tiger inside the elephant enclosure isn't "just" another animal.
- kiitos 1y agoYou're simply wrong. If I call a function and it fails, then at the base level it doesn't matter if it failed because "no more disk space" or because "input values are invalid" -- the thing failed, in both cases. The caller needs to deal with that failure, in all cases. Now exactly how it deals with that failure might depend on properties of the error, sure, but the control flow of the program is the same in any case.
- hobs 1y agoTo be fair Rust doesn't have sum type it has enums, which I feel like you could do in Go, but I haven't read the arguments.
- 9rx 1y agoTechnically, Rust has sum types (a.k.a. tagged unions) that use an enum to generate the tag. So, while enumeration is involved, sum types is still a better description of what it is.
- hmry 1y agoEnum is what they call it (perhaps to appear more familiar to C++ programmers?), but from a computer science standpoint they are classic sum types.
- kbolino 1y agoIt's an interesting idea. Right now, you can do something like this: res := someFunc() // func() any switch v := res.(type) { case error: // handle error case T: // handle result default: panic("unexpected type!") } Then, presumably, a T|error sum type would be a specialization of the any type that would allow you to safely eliminate the default arm of the switch statement (or so I would like to think -- but the zero value issue rears its ugly head here too). Personally, I'd also like to see a refinement of type switches, to allow different variable names for each arm, resulting in something like the following hypothetical syntax: switch someFunc().(type) { case err := error: // handle error case res := T: // handle result } However, there's no real syntactic benefit for error handling to be found here. I like it (I want discriminated unions too), but it's really tangential to the problem. I'd honestly prefer it more for other purposes than errors.