| He still has a point. In theory you might need a language like Idris/Agda, but in praxis it still makes a big difference. It is true that you will see that a function can return an error and that you choose to ignore it. It's also true that you can do the same in many other languages that use sumtypes. But it is still different. Because while ignoring an error in go is as easy as putting an underscore next to the happy-case, in languages with sumtypes that doesn't work. The equivalent in other languages would be to return a struct and then just access one value and ignore the other one. In that case, the practical implications would be the same. But when using a sumtype, a few things change. First, you can not just access the happy-case value, you are (or at least can be) forced to also "access" the unhappy-case value. Either in a pattern match, in a fold-function and so on. You know have to return something, even if it is an empty value our "escaping" by throwing an error. On top of that, what happens if a function can partially succeed? Take a graphql request as a practical example where this is quite common. With Go's style of error handling, how do you model that? I.e. say you need to redactor a function that previously either succeeded or failed into one that now can partially succeed and fail. In a language with sumtypes I would now switch from a sumtype Success|Error to more complex type Success|Error|PartialSuccess which makes it a breeze to refactor my code because the compiler will tell me all the places where I have to consider a new case and what it is. I'm genuinely curious, how would you model that in Go and what implication would such a refactoring have on existing code? I imagine it would be quite different in praxis. |
I would probably simply do in C the same thing as usual:
This is compatible with old callers, even, who treat any nonzero result as failure and any zero result as complete success (the normal pattern in C).Yes, the caller needs to check the result and avoid looking at out2 if you dont get SUCCESS and avoid looking at out1 if you get FAILURE. But this sort of thing is de rigeur in C. Your compiler (or a linter, and optional warning flags are essentially linters anyway) will warn you if you ignore the result and if you switch on the result will warn you if you ignore a case.
But obviously it is left up to you to avoid the "dont touch X if Y" stuff. Eh, that is in my experience not the hard bit of writing C. The hard bit is anything involving dynamic lifetimes or shared mutable state. The nice thing is that you can avoid this in C! Most people don't. The easy path is calling malloc everywhere and getting yourself into a muddle. The simple path, which is better in the long run, is to use values and sequential, imperative code. And if you do that, you realise that C's design makes way more sense. That is how it was designed to be used. Dynamic lifetimes of objects? It is like trying to use Rust to represent linked lists. People that say 'Rust sucks because double linked lists lol' are morally equivalent to people that say 'C sucks because malloc and free lol', it is like.... Yeah you aren't meant to do that!