3 ms·
Multiple return values are a kludge. Combined with nil being only usable for a pointer value, it's entirely too easy to accidentally not process the error and a
by stouset 10y ago
Multiple return values are a kludge. Combined with nil being only usable for a pointer value, it's entirely too easy to accidentally not process the error and accidentally use the non-error return value (bool, int, or other primitive type).
Go should have had Rust-style enums that let you do things like Option<T> and Result<T,E>. This way, you are forbidden by the compiler from ever extracting an invalid value from a function that returned an error (or returned no value).
- curun1r 10y agoI agree wholeheartedly. Also of note, the combinators Rust offers on Option/Result make it possible to chain multiple operations that could fail and then check for the first error that occurred. In Go, you have to check each potential error immediately after it's potential return. This allows Rust to be just as clear as Go while being more concise and enforcing at compile-time that errors are handled. Propagating errors out of a function is also easier given try!/?. I really don't see a downside of Rust's approach compared to Go's after having programmed in both. But, then again, Rust's approach requires generics and I know Go's team doesn't want to go in that direction.
- iand 10y agoChaining and failing on first error is only useful for the most trivial of error situations. Non toy programs handle errors with retry strategies, alternate algorithms and fallbacks which may all be contextual.
- ovao 10y agoIt depends on the problem domain, but it's been my experience across a few domains that many errors are of the "trivial" type you've described, in that recovering from an error by taking some alternative path is usually not very valuable. A bail-out-on-any-error approach doesn't necessarily mean it's a "toy program". Nothing in Rust (or Go, even) ordinarily precludes the type of retry approach you're describing, of course.
- ngrilly 10y ago> I know Go's team doesn't want to go in that direction. I disagree: https://github.com/golang/proposal/blob/master/design/15292-generics.md https://github.com/golang/proposal/blob/master/design/15292-...
- piaste 10y agoDid you check the discussion thread for that proposal? It was rejected by the mailing list devs: https://github.com/golang/go/issues/15292#issuecomment-221986459 https://github.com/golang/go/issues/15292#issuecomment-22198...
- ngrilly 10y agoYes, I read it. Here is the comment you linked: > Ian's proposal demands too much. We cannot possibly develop all the features at-once, it will exist in an unfinished state for many months. > In the meantime, the unfinished project cannot be called official Go language until done because that will risk fragmenting the ecosystem. > So the question is how to plan this. > Also a huge part of the project would be developing the reference corpus. developing the actual generic collections, algorithms and other things in such a way we all agree on that they are idiomatic, while using the new go 2.0 features The author of this comment is not a member of the core team, as far as I know, and the comment doesn't suggest the proposal was rejected.
- wahern 10y agoMultiple return values are most definitely not a kludge. They're an awesome abstraction that few languages actually provide because of the implementation complexity. The kludge is where a language forces you to use a struct or some similar compound type when it's most natural for a function to return multiple values. And it's symmetric with function parameters. If you think multiple return values is a kludge, then for the same reasons so would multiple parameters. After all, you can always curry multiple parameters, or shoe-horn multiple parameters into a compound objet like a struct. Now whether it makes sense to leverage multiple return values as a substitute for specialized error types, or for exceptions, is a different question. But if you don't see the value in multiple return values than I think it's hard to assess things objectively. I program a lot in Lua and love multiple return values. And in Lua the standard practice is to leverage multiple return values with exceptions. E.g. local x, y = assert(bar()) where on success bar is expected to return a first value that evaluates to true in a boolean context. If so then assert just returns that first value and any subsequent values. If not then it throws the second value as an exception, which usually is expected to be an error description string or an object that can be stringified, but assert doesn't care about the type. Note that assert() isn't a specialized function. You can implement it in pure Lua. Multiple parameters and multiple return values are generalized as lists (similar to Perl) which also can be used when, e.g., initializing a table. function foo() return "one", "two", "three" end for i,s in ipairs({ foo() }) do print(i, s) end prints 1 one 2 two 3 three Of course these things can be abused and they're not strictly _necessary_. But they're powerful abstractions that can prove very useful, especially in the context of functional programming styles. Go seems simple on the outside but is actually very elegant and sophisticated internally. Multiple return values, lexical closures that automatically capture by reference and which can escape their original scope without any fuss, lightweight stackful coroutines (stackful meaing no decoration required for recursively invoking yielding functions) that automatically adjust their stack size. You usually only see these things in interpreted and dynamically typed languages, if at all, because they can be very difficult to implement efficiently with an optimizing compiler. Similar concepts in C#, C++, Rust are not nearly as powerful yet still require syntactic kludges (like decoration) to help drive the kludgy implementations. That Go implements these things seamlessly, while compiling straight to native code, is quite a testament to the Go team. Most of the attention these days is given to stronger typing systems. And that's fine. Go doesn't win too many points on that front, although its duck typing has unique advantages, even over traits. But it's not reasonable to overlook all the other things that Go does extremely well and almost without equal for an optimizing, AOT-compiled, strongly typed language. All of these things come with trade offs. Typing systems are hardly the be all, end all of language design. They're usually not even the most interesting part, nor the part most relevant to productivity. What's unique about Rust, for example, isn't its typing system, but its borrow checker. And the borrow checker drives directly and indirectly much about their choices with other aspects of the language. Like I said, multiple return values are difficult to implement underneath the hood, and in a language like Rust might require some really odd declaration syntax and have other unforeseen consequences. Thus you see them reconceptualizing multiple return values in terms of a kind of polymorphic compound type. That's not intrinsically better--if anything it can be seen as a kludge--but in any event it's driven by other needs than pure abstract functionality and should be not only criticized that way, but appreciated that way.