5 ms·
"No proper arrays/slices" is definitely up there, but I'd nominate "no dedicated error handling" for the #1 spot, by virtue of how much trouble it causes compar
by oconnor663 3y ago
"No proper arrays/slices" is definitely up there, but I'd nominate "no dedicated error handling" for the #1 spot, by virtue of how much trouble it causes compared to how easily it could be fixed. Go has a reasonable and remarkably lightweight approach here: add just enough of a tuple types feature to support multiple return values, and then make the rightmost return value an error flag by convention. Easy! No more "zero indicates success unless the return type is a pointer" nonsense. Bonus points for something like `error` that gives tooling a clue, but even that we could live without.
- kaashif 3y ago> add just enough of a tuple types feature to support multiple return values What's the difference between returning a two element struct and a pair of value and error? I never understood why "multiple return values" is a thing anyone should care about.
- achates 3y agoWouldn't you have to define a new struct type for every function return type?
- GeneralMayhem 3y ago1. In a world without templates/generics, you have to manually define a different such struct for every possible return type. Can be macro'd at the function definition, but it's ugly. 2. Without first-class support, it's very difficult to assert that the error is actually being checked. Consider: int someVal = getSomeVal(...).v; doSomethingWith(someVal); vs. (int someVal, error e) = getSomeVal(...); doSomethingWith(someVal); Which is easier to catch at code-review time, or even at linter time? In most languages that don't support multiple return values, both problems are solvable with templates or local equivalent - see, for instance, absl::StatusOr for C++ (https://abseil.io/docs/cpp/guides/status https://abseil.io/docs/cpp/guides/status).
- signa11 3y ago> 1. In a world without templates/generics, you have to manually define a different such struct for every possible return type. Can be macro'd at the function definition, but it's ugly. one trivial possibility/option is to define in-out parameters, and just return errors: <error-type/int/…> foo(T*, <params>) will that not be ok ?
- 0x09 3y ago>Can be macro'd at the function definition, but it's ugly. I wonder if typeof in c23 has changed this at all. Previously there was no sense in defining an anonymous struct as a function's return type. You could do it, but those structs would not be compatible with anything. With typeof maybe that's no longer the case. e.g. with clang 16 and gcc 13 at least this compiles with no warning and g() returns 3. But I'm not sure if this is intended by the standard or just happens to work. struct { int a; int b; } f() { return (typeof(f())){1,2}; } int g() { typeof(f()) x = f(); return x.a + x.b; } edit: though I suppose this just pushes the problem onto callers, since every function that does this now has a distinct return type that can only be referenced using typeof(yourfn).
- kaashif 3y agoI think most of the time people really want a sum type, NOT a tuple. i.e. you can get back either a value OR an error, but not both. In Go, there's the pattern of returning nil, err, which is a hint that we really want a sum type there. > In most languages that don't support multiple return values, both problems are solvable with templates or local equivalent I don't really see the difference between tuples in C++ and multiple return values in Go (since you cite C++ as a language that doesn't have multiple return values). val, err := do_something() or auto [val, err] = do_something(); What's the real difference there? Syntactic sugar?
- GeneralMayhem 3y agoI agree, you really want a sum, not a tuple - that's why my example was absl::StatusOr<T>, not std::pair. But a tuple plus tooling support (e.g., linters in Go that don't allow you to not use the returned error value) can emulate a sum type.
- oconnor663 3y agoYeah exactly, there's like zero difference to the compiler. That's why it's such an easy feature. (To be fair you probably also want some syntactic niceties like destructuring bindings.) But clearly in practice, no one wants to define a new named struct type for every function that needs to return an error. I think part of the pain is that, without some sort of "auto" type deduction feature, you force the caller to type out the name of the return type most of the time, in addition to the name of the function. And yeah, if I had type out StringAndError or IntAndError a million times to use a library, I'm sure I'd hate that library :)
- 2h 3y agobecause its ugly: package hello func good() (int, error) { return 0, nil } func bad() *struct { int error } { return nil }
- generalmorale 3y ago> I never understood why "multiple return values" is a thing anyone should care about. For the same reason people care about multiple parameters. There's no reason that the data coming out of a function should be in any way more constrained than the data going into the function.
- kaba0 3y ago> Go has a reasonable and remarkably lightweight approach here Go literally hardcoded the same error-prone errno-like error handling, it just has some syntax sugar now. It still doesn’t compose, is unreadable and gives only the impression of properly handled error cases. It even fails to return a proper sum type, so now you get a possible important value and an error value as well.