6 ms·
Result is better because it actually encodes the correct situation. You either get a file or an error. Not neither, not both. Go's encodes instead "you may or
by kadoban 3y ago
Result is better because it actually encodes the correct situation. You either get a file or an error. Not neither, not both.
Go's encodes instead "you may or may not have a file" and "you may or may not have an error". Not the same thing, and extremely rarely what you want, IME.
Other languages also do a better job of helping you verify that you actually handled both cases too.
By the way I wouldn't say we need Monad here, I'd be happy if Go could at least encode Sum types.
- XorNot 3y agoThis seems like a distinction without a difference. In reality, in code, I'm still doing something like `if (!result.failed) { do the thing } else { do the other thing }` Like...this makes no difference to my ergonomics at all. In many cases it's arguably worse because now 1 token is potentially representing two very different types I want to deal with. The primary benefit to me seems like it's more to do with the ease of generic code handling: everything can be Results, and then I can evaluate them all to see if any of them are errors, which in turn makes failing out of many different operations easier - I'm not handling a file type, a string, some numbers etc.
- mkehrt 3y agoIn rust the type system enforces you check the error. In go, it doesn't. (Because rust error types are enums/sums and go error types are structs/products). This seems like a huge difference. > In many cases it's arguably worse because now 1 token is potentially representing two very different types I want to deal with. Yes, that's what an enum/sum type is? That's the whole point.
- philosopher1234 3y agoIn rust it is not possible to use incorrectly, and in go it is, sure. But whether it is possible or not is only one dimension. Does it matter that it’s possible to misuse errors in Go if it virtually never happens? I just don’t find the point about what is possible interesting. The other trade offs around readability, ergonomics, and so on seem more impactful.
- go_explore_5 3y agoIt depends if you find value in exhaustive pattern matching. From years of using C's switch statements, I'm not going back.
- consilient 3y ago> Does it matter that it’s possible to misuse errors in Go if it virtually never happens? I just don’t find the point about what is possible interesting. If you knew for sure that it virtually never happened, maybe not. But you don't. At best you know that a few particular individuals you're familiar with never mess it up (but then again, consider the people who "virtually never" write incorrect C). You can't trust random packages you haven't vetted, and you certainly can't trust code written by your junior software engineers. > The other trade offs around readability, ergonomics, and so on seem more impactful. Sum types have significantly better ergonomics. `(Result | null, Error | null)` takes four branches to handle properly, whereas `Either Result Error` takes two. And of course things get much worse once you're more than one layer deep, which in a language as procedural as Go you almost always are.
- nulld3v 3y ago> virtually never happens Ah yes, like it "never happened" in the Kubernetes project? - https://github.com/kubernetes/kubernetes/pull/60962 https://github.com/kubernetes/kubernetes/pull/60962 - https://github.com/kubernetes/kubernetes/pull/80700 https://github.com/kubernetes/kubernetes/pull/80700 - https://github.com/kubernetes/kubernetes/pull/27793 https://github.com/kubernetes/kubernetes/pull/27793 - https://github.com/kubernetes/kubernetes/pull/110879 https://github.com/kubernetes/kubernetes/pull/110879 I can find tons of these, just by searching any larger Go project's Github. Here's one from docker too: https://github.com/moby/moby/pull/10321 https://github.com/moby/moby/pull/10321 What about from CockroachDB? https://github.com/cockroachdb/cockroach/pull/74743 https://github.com/cockroachdb/cockroach/pull/74743 Even the linter missed this one!
- philosopher1234 3y agoOf course it happens, I’ve done it. But it’s obvious, it’s the least interesting aspect of the debate. And I think it is less impactful than e.g making exceptions easy for devs to ignore.
- randomdata 3y agoThe Result approach believes that the producer knows what is best for the caller regardless of who the caller is. The Go approach believes that the producer shouldn't assume it knows the caller. I'm not sure one is better than the other, just different tradeoffs.
- iudqnolq 3y agoDoesn't the producer know best whether the producer failed?
- randomdata 3y agoDoes the caller care? By day I work with a team in a language that sees errors ride on the exception handling system. Staying within the original example, I see code like this all the time (too often, even, but that's another topic for another day): try { file = getFile() } catch(/* ... */) { fileUnavailable() } Here, the assumption of getFile that the caller wanted an error was incorrect. A Result-using language would end up in a similar place. Idiomatic Go says leave it to the caller. Like above, when only wants to know if there is "file or no file" without concern for why there is no file, then: file, _ := getFile() // The second return argument is an error. if file == nil { fileUnavailable() } I doubt either way makes much difference in this contrived example, but the difference shows up when it extends out into real code. There are plusses and minuses to each way of seeing the world. Tradeoffs, as always.
- nulld3v 3y agoBut in Rust you can do the same, I do this all the time: let _ = fs::mkdir_all() // Error ignored, Rust will not complain because you explicitly assigned to _ Or if the function returns something I need but I don't care about the error: let Ok(file) = get_file() else { file_unavailable(); return } upload_file(file); Or this: let file = get_file().unwrap_or_default()
- randomdata 3y ago
- quicklime 3y agoI love Rust but honestly the Go way works fine, even if it isn’t as strictly correct as Rust. I don’t think I’ve ever seen a case where a Go function returned neither a value nor an error, or both a value and an error. What I like better about Rust, and what I think most people are actually complaining about with Go, is that syntactic sugar like the ? operator and functions like unwrap(). It’s a lot more concise and your application logic doesn’t get lost in verbose error checking code.
- kadoban 3y ago> I don’t think I’ve ever seen a case where a Go function returned neither a value nor an error, or both a value and an error. That's kind of the point. The type system should be powerful enough to disallow those cases then. In practice, I've seen both, always accidentally. I've also (more commonly) seen a lot of confusion and annoyance around: Okay, so this has to return a pointer for the error case, should the caller check that? If not, how do we square that with checking for nil pointers being generally a pretty good rule? If we do check, our unit test coverage has a blemish for every call since nothing can hit that. If we skip it being a pointer, then it's a zombie object. It's just a lot of cognitive load and bikeshedding around an issue that shouldn't exist.