4 ms·
Go has specific goals like not hiding control flow. This would go against those goals, at least the ways people have thought to do it so far.
by ok_dad 1y ago
Go has specific goals like not hiding control flow. This would go against those goals, at least the ways people have thought to do it so far.
- tialaramex 1y agoI don't see how Try (the ? operator) is hidden control flow. It's terse, but it's not hidden.
- ok_dad 1y agoI personally agree, but I’m not the go team. The hidden control flow was specifically called out but about the try keyword. I like the ? and similar ways of checking nulls, but personally I don’t mind the verbosity in go, even if there are footguns.
- jchw 1y agoIMO: because it behaves like structured control flow (i.e. there is a branch) but it doesn't look like structured control flow (i.e. it doesn't look like there is a branch; no curly braces). I don't think there's a single other case in the Go programming language: it doesn't even have the conditional ternary operator, for example.
- LoganDark 1y ago`return` doesn't have braces either.
- jchw 1y agoClosest thing to a real interblock branch without braces, IMO, is `break` and `continue`, but those are both at least lone statements, not expressions. It "looks like" control flow. Personally, I don't count `return`, I view it as it's own thing from a logical standpoint. Obviously if we were talking about literal CPU doing a jump, well then a lot of things would count, but that's not what I mean in the frame of structured control flow and more in the realm of implementation details.
- tialaramex 1y agoI could have sworn that like most modern languages Go has `break label` although being statement oriented it doesn't have `break label value`.
- LoganDark 1y agoI love Rust's ability to return a value from a loop, it's so nice.
- jchw 1y agoIt does. Hell, Go also has a goto statement as well, although obviously that's unstructured control flow. A more refined version of what I originally said would say "conditional branch" instead of "branch", but I'll admit that my original message should have been worded more carefully. I think people understood it, but taken literally it's not a strong argument.
- _kidlike 1y agothe obvious solution is try-catch, Java style. Which I'm surprised it's not even mentioned in the article. Not even when listing cons that wouldn't have been there with try-catch. But of course that would hurt them and the community in so many levels that they don't want to admit...
- jchw 1y agoI strongly do not like try/catch. Just to list the limitations of exceptions that come to mind, - try/catch exceptions obscure what things can throw errors. Just looking at a function body, you can't see what parts of the functions could throw errors. - Relatedly, try/catch exceptions can unwind multiple stack frames at once, sometimes creating tricky, obscure control flow. Stack unwinding can be useful, especially if you really do want to traverse an arbitrary number of stack frames (e.g. to pass an error up in a parser or interpreter, or for error cases you really don't want to think about handling as part of the normal code flow) but it's tricky enough that it's undesirable for ordinary error handling. - I think most errors, like I/O errors, are fairly normal occurrences, i.e. all code should be written with handling I/O errors in mind; this is not a good use case for this type of error handling mechanism—you might want to pass the error up the stack, but it's useful to be confronted with that decision each time! With exceptions, it might be hard to even know whether a given function call might throw an I/O error. Function calls that are fallible are not distinguishable from function calls that are infallible. - This is also a downside of Go's current error handling; with try/catch exceptions you can't usually tell what exceptions a function could throw. (Java has checked exceptions, but everyone hates them. The same problem doesn't happen for enum error types in Rust Result, people generally like this.) (...But that's certainly not all.) Speaking just in terms of language design, I feel that Rust Result, C++ std::expected, etc. are all going in the right direction. Even Go just having errors be regular values is still better in my opinion. (Still, traditional exceptions have been proposed too, of course, but it wasn't a mistake to not have exceptions in Go, it was intentional.)
- deleted 1y ago[deleted]
- 1y ago
- 9rx 1y agoEven the laziest programmer is going to want to wrap the error with another error, and usually you will want to do more than that. You can put that in-band, with something like: v := funcWithError()? err := { return fmt.Errorf("lazy programmer wrapping: %w", err) } But in that case what have you really gained? Or you can do some kind of magic to allow it to happen out of band: // found somewhere else in the codebase func wrapErr(err error) error { if err == nil { return nil } return fmt.Errorf("lazy programmer wrapping: %w", err) } v := funcWithError()?(wrapErr) But that's where you start to see things hidden.
- kiitos 1y agoCurrently if you want to return from a function/method you need to type `return` in the source code. And return is a single expr, it can't be chained or embedded, and in almost all cases it exists on its own line in the file. This is an important invariant for Go, even if you don't understand or buy its motivation. `?` would fundamentally change that core property of control flow. In short, chaining is not considered to be a virtue, and is not something that's desired.
- skywhopper 1y agoThat only covers one tiny case among several possible error flows. Why add special syntax for that?
- LinXitoW 1y agoIsn't defer hidden control flow? The defer handling can happen at any point in the function, depending on when errors happen. Exactly like a finally block.