> As someone who's not a day-to-day developer/software engineer: the Go code is more readable.
COBOL is what you get when you optimize for readability by people who don't know how to program.
> The Rust is interesting, for sure, but I don't know what "?" is doing. I like the use of the question mark as it makes the act of calling the function a question - did it work or not? But what's not obvious is what happens if it did not work? What happens then? Where does the error object go? Is there something that represents the error? Hidden magic isn't good magic, in my opinion. Just be explicit.
It gets returned to the next level up, exactly like in the analogous Go code above. And that's how it always works, so you don't have to try to figure it out separately for each use of it you see.
> Another issue with the Rust example is discipline. I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? (What happens if I do that and there's an error? This isn't explicit enough.) Go, in comparison, does the opposite: it forces you to handle the error or literally ignore it. You cannot forget to deal with an error in Go.
You actually can't do that and will get a compiler error if you try. Rust's type system distinguishes between "a number" and "either a number or an error". On the other hand, Go will let you forget to handle an error, if you do "res1, err1 = canFail()" but then forget to return an error yourself in the "err1" case. Rust's sum types prevent that mistake entirely.
> And of course, Go gives you an error object you can work with (something I'm sure Rust does too, but it's not obvious to me as a none Rust developer.)
Indeed it does. I consider it a good thing that you don't have to deal with language features you're not currently using, though.
> I prefer my languages to be statically typed, statically linked, and very explicit in their syntax. It results in a bit of extra work up front for compile and run time safeties, and the ability to easily re-read the code at a later date.
But Rust fits the bill for all of those things, and if you care about compile time safety, it does so way better than Go.
Let's assume an error was returned. You realize from the error that there is a bug in the code. Now you're tasked with debugging the code given the error that was presented.
Which function did the error come from? Who knows. And what if canFailA/canFailB return errors from other functions up the stack the same way? Now you've got a massive tree of possibilities to try and work through. A complete nightmare.
In the real world you would take the error and do something with it. Even if you still end up returning an error, it won't be the error you received. It will be a new error that provides pertinent information about the situation.
Go brought forth a legitimate "try" proposal that was very similar to the Rust example and, while well received on the surface, it failed because it was determined that you couldn't possibly use it, at least not beyond toy examples, because of the above.
Presumably Go could introduce a concept of error (it currently has none) which could then include information like stack traces to help with that problem, but that's way more than what you're talking about, and would still lack all the other benefits you get when you handle errors as soon as you get them, not blindly pass them up the stack.
Rust's solution may be nice for Rust, being designed for that pattern. It wouldn't fit well in Go without radically rethinking the language.
> On the other hand, Go will let you forget to handle an error, if you do "res1, err1 = canFail()" but then forget to return an error yourself in the "err1" case.
You can also forget to return res1 (per the original example).
This is a real problem that should be solved, but it's not a problem of errors. It's a problem of values in general. Remember, the Go language has no inherit concept of error. Anything that we happen to call an error is actually just a user-defined type, same as any other type a user might define (birthdate, order number, stock price, etc.).
To frame it as a problem of errors shows a misunderstanding of the problem.
> Which function did the error come from? Who knows. And what if canFailA/canFailB return errors from other functions up the stack the same way? Now you've got a massive tree of possibilities to try and work through. A complete nightmare.
This is why the popular approach in Rust is to add "failed to do X" to the error before returning it, which is handled by popular libraries.
An example of the effect:
Failed to start, caused by
Failed to load config, caused by
File not found (the error from the OS)
> This is a real problem that should be solved, but it's not a problem of errors. It's a problem of values in general. Remember, the Go language has no inherit concept of error. Anything that we happen to call an error is actually just a user-defined type, same as any other type a user might define (birthdate, order number, stock price, etc.).
The concept that a function can fail is pretty fundamental, ignoring it at the language level is like saying "a function failing is not common enough to address consistently"
> The concept that a function can fail is pretty fundamental
Not at all. This is a grave misunderstanding of computing. Functions fundamentally can't fail. They can only enter different states. Only under exceptional circumstances, like the programmer screwed up or the machine is literally on fire, could they fail.
Indeed, Go does provide a method for dealing with exceptional circumstances (what we often call exceptions for short). See: panic/recover.
COBOL is what you get when you optimize for readability by people who don't know how to program.
> The Rust is interesting, for sure, but I don't know what "?" is doing. I like the use of the question mark as it makes the act of calling the function a question - did it work or not? But what's not obvious is what happens if it did not work? What happens then? Where does the error object go? Is there something that represents the error? Hidden magic isn't good magic, in my opinion. Just be explicit.
It gets returned to the next level up, exactly like in the analogous Go code above. And that's how it always works, so you don't have to try to figure it out separately for each use of it you see.
> Another issue with the Rust example is discipline. I don't know Rust, but the example you've given implies I can not include "?" in the call and just ignore any errors entirely? (What happens if I do that and there's an error? This isn't explicit enough.) Go, in comparison, does the opposite: it forces you to handle the error or literally ignore it. You cannot forget to deal with an error in Go.
You actually can't do that and will get a compiler error if you try. Rust's type system distinguishes between "a number" and "either a number or an error". On the other hand, Go will let you forget to handle an error, if you do "res1, err1 = canFail()" but then forget to return an error yourself in the "err1" case. Rust's sum types prevent that mistake entirely.
> And of course, Go gives you an error object you can work with (something I'm sure Rust does too, but it's not obvious to me as a none Rust developer.)
Indeed it does. I consider it a good thing that you don't have to deal with language features you're not currently using, though.
> I prefer my languages to be statically typed, statically linked, and very explicit in their syntax. It results in a bit of extra work up front for compile and run time safeties, and the ability to easily re-read the code at a later date.
But Rust fits the bill for all of those things, and if you care about compile time safety, it does so way better than Go.