Both Go and Rust hand the caller an error and the caller then must do something with the error. In both Go and Rust, you can assign the error to underscore and ignore it.
Java is different but we aren't talking about Java right now.
The only difference is that in Go the function can (and must) still provide a value for "file" even when there is an error. In Rust you can kind of do something similar, by giving the File struct a default value (that's what "unwrap_or_default()" is) but it isn't exactly the same. I would argue that such a case is extremely rare though.
This is simply not true. Go preaches that values should always be useful. Given a return signature (T, error), the value of type T can be used irrespective of what is contained in the value of type error. They are independent states.
> The only difference is that Go provides a value for "file" when there is an error.
Yes, that is ultimately the difference. Result takes what are independent states and tries to make them dependent. Result<T, error> assumes that if there is a value of type error then the caller would not make use of the value of type T.
Often they will be dependent for all practical purposes. It is not an unreasonable assumption to assume that they are, especially if you know the caller. However, Go believes you cannot make that assumption. That you have to let the caller determine what it finds useful, not what you think it might find useful. Ease of code reusability is clearly a design goal of Go, so the idea that you can't get to know your callers and how they will use your function isn't unwarranted.
Tradeoffs, as always.
>However, Go believes you cannot make that assumption.
This is trying to twist a weakness of Go's type system as a virtue. In Go I always have "A or B, including neither and both". I can have that in Rust or Haskell if I want, but usually I want "A or B, never both or neither" and rarely "A or B, sometimes both but never neither" which Rust and Haskell can do but Go cannot. This comes from Go using product types as a poor replacement for sum types.
No. There is no discussion about type systems taking place here at all. The discussion is about patterns where the producer or the consumer is in control. Specifically, Result puts the producer in control. Idiomatic Go (T, error) sees the consumer in control.
There is likely no language in existence that prevents you from choosing. You can write code where the producer is in control in Go, and you can write code where the consumer is in control in every other. These are not features of a language, although language idioms do often push developers one way or the other.
>Idiomatic Go sees the caller in control.
This is simply not true. If the producer never returns A+B, then the consumer is never going to magic that into existence. The producer is always in control of what the producer returns.
>Specifically, Result puts the producer in control.
Result is just a convenience type for the very common case of a function returning "A or B, but never both or neither." If your producer does not match that, you are not forced by the language to use Result. In Go, you are always forced by the language to return a value of type "A or B, including both or neither." The consumer isn't any more in control, it just has to guess whether "both or neither" are possible return types. In Rust/Haskell/Ocaml/Java, hell even C++ with variant or C with union, I can return something or with a more restrictive type. Honestly Go is uniquely incapable of representing these cases in its type system and control flow.
No. The discussion opened to say that Result is better than the pattern used in Go. In other words, dependent state is better than independent state. I said that dependent state puts the producer in control, while independent state puts the consumer in control and that there are pluses and minuses to each approach, with neither being better, just different tradeoffs.
We continued to discuss that control and how Result, exceptions, and the (T, error) pattern play into that. Somewhere in the middle was a regularly scheduled Rust advertisement, but following that returned to the same topic. A topic that is not, and never has been, about type systems. And it isn't even about any particular programming language, even if Go was used to call out a pattern that the original commenter didn't have a better name for.
> If your producer does not match that, you are not forced by the language to use Result.
So, in other words: Dependent state puts the producer in control, while independent state puts the consumer in control and that there are pluses and minuses to each approach, with neither being better, just different tradeoffs...?
It is curious how the word Go causes minds to shut down.
You are trying to paint a false dichotomy between Rust's Result<T, error> and Go's (T, error). In reality the dichotomy is between sum types (which Result is a very simple case of) and languages without sum types, of which Go is one of a very small set. The key difference is that Result is available as an option in Rust/Haskell/Ocaml/etc, and I'm free to not use Result if it's not the right fit, where in Go the only option is to use a product type.
Result<A,B> and the ? operator are shortcuts for concisely handling the very common case of handling "A or B, but never both or neither." If your producer doesn't fit that, you're free to define your own sum type. In Go, you are simply unable to represent these and must cover all four cases (or eight, or sixteen, or thirty-two, etc, as the function grows in complexity).
Nothing I'm saying is specific to Rust either, despite your appealing to conspiracies, and Rust's ideas are not new, not by decades. Haskell, Ocaml, Java, C++, typescript, etc, (even C with a bit of squinting) all have basic sum types and can encode "A or B, but never both or neither" and "A or B or both, but never neither" in their type systems and control flow in a way that Go cannot. Rust didn't pioneer the idea of a basic sum types, Go is just uniquely (among popular statically types languages from the last 50 years) incapable of representing them.
>So, in other words: Dependent state puts the producer in control, while independent state puts the consumer in control and that there are pluses and minuses to each approach, with neither being better, just different tradeoffs...?
No, this is complete nonsense. In Go, if I never write "return nonNilFile, nonNilError", the consumer cannot magic that into existence. Your entire position about languages being consumer controlled vs producer controlled is entirely nonsense. Consumers cannot rewrite the code of the functions they call.
No, it wasn't and you didn't. Result is one option among many in Rust which covers the common case with concise syntax, where (T, error) is the only option in Go. I am free to return a Success<T>, PartialSucceess<T + Error>, Failure <Error> in Rust/Java/C++/Ocaml/Typescript. I think you're operating on some profound misunderstandings about Rust, Haskell, Ocaml, Java, Typescript, C++, and even C. I don't think you're open to trying Rust, but maybe you'd be open to playing with std::variant in C++ or manually tagging unions in C, to see what those languages can do that Go cannot. Try writing a C++ function that returns std::variant between T, std:::tuple<T, Error>, and Error. That's only 3 cases, which Go cannot do.
it does not have to guess
convention dictates either one or the other
is convention enough? is it roughly the same as compiler-enforced rules? you're free to say "no" but it is not like a guarantee
Convention is just an educated guess.
>is it [convention] roughly the same as compiler-enforced rules?
No. The answer is no. I'm not only free to say "no," but if I'm honest and truthful I am compelled to say "no."
at some absolute level yes, but at any pragmatic level no, definitely not
> No. The answer is no. I'm not only free to say "no," but if I'm honest and truthful I am compelled to say "no."
nope! wrong. do not pass go, etc. -- it's a spectrum
You are simply incorrect, there's no nuance or room for interpretation. A compiler can guarantee "A or B, but not both or neither", "A or B or both, but never neither", or even "A or B or both or neither" but convention for (A, B) in Go cannot, therefore it is not as good. Go's conventions are not as good as a decent type system, there's no spectrum about it; Go is always "A or B or both or neither" with no ability to exclude impossible/nonsense cases. In Go if I want to write robust, correct code I must always handle all 2^N cases for N arguments, which no one actually does.
this is not correct
but i'm not sure how to convince you of this truth, so (shrug)
>but i'm not sure how to convince you of this truth, so (shrug)
You'd need some pretty compelling evidence to convince me that something that is not guaranteed is as good as something that is guaranteed, so I can see why you're struggling. With your admissions, all I can really say is good luck convincing me or anyone.
This is already false. Not all values are useful, for example, the value of this string is not useful to you: "i have cheese".
Without knowing the caller, values can be "possibly useful" at best.
> Often they will be dependent for all practical purposes. It is not an unreasonable assumption to assume that they are, especially if you know the caller. However, Go believes you cannot make that assumption. That you have to let the caller determine what it finds useful, not what you think it might find useful.
In Go, functions always assume the caller needs both a valid value of T and error, even when T has no value. Functions should synthesize some value for T should T not currently have a value at return time.
In Rust, most functions assume the caller needs either a valid value of T or Error. Should a valid value of T exist at return time, it will be discarded.
Both languages make assumptions.
> Tradeoffs, as always.
Assumptions come with tradeoffs, however, not all tradeoffs are equal in consequence and possibility.
99% of the time, a caller does not need T if there is an Error. Therefore, why make this tradeoff?