No, actually, an operator that's essentially a macro for this entirely boilerplate operation would be less terrible, exactly the same decision Rust made for the exact same reason. So would Cox's proposal, so would others. Doing nothing, as a permanent solution, because you can't figure out which of the better things you should do, is not a virtue. You may be confusing it with the virtue of holding out on simpler solutions while a better solution is implemented, or the virtue of not solving things which aren't problems, but this is a problem and they have intentionally blocked the solution indefinitely.
Rust's try! macro was† "essentially a macro for this entirely boilerplate operation" but the Try operator ? is something more interesting because in the process they reified ControlFlow.
Because implementing Try for your own custom types is unstable today if you want to participate you'd most likely provide a ControlFlow yourself. But in doing that you're making plain the distinction between success/ failure and early termination/ continuing.
† Technically still is, Rust's standard library macros are subject to the same policies as the rest of the stdlib and so try! is marked deprecated but won't be removed.
It's just simply not the cause that error handling is an "entirely boilerplate operation", nor that any kind of macro in Go "would be less terrible" than the status quo, nor is it true that decisions that Rust made are even applicable to Go. Believe it or not, the current approach to error handling actually does work and actually is better than most/all proposals thru the lens of Go's original design intent.
Is entirely boilerplate, and a language feature could generate it (and in Rust, does). This is not the same statement as 'all error handling is boilerplate', which is obviously false, which is why I didn't say that. Condensing that particular snippet down to `?` would be less terrible than the status quo, where the status quo is every function being filled with twenty copies of it drastically reducing readability. The situation is exactly the same as with old Rust, where:
let foo = match expr {
Ok(val) => val,
Err(e) => return e,
};
Was entirely boilerplate. Rust noticed that this was a problem and solved it. Go's status quo is not better than pre-`?` Rust's status quo; it does nothing pre-`?` Rust didn't. Go just doesn't solve it.
It is not actually the original design intent of Go to make every function 50% boilerplate garbage by LoC. Go is extremely full of 'helpful' happy-path short functions that leave you reimplementing lots of stuff more verbosely the moment you step off the happy path, inclusive of happy paths that do partially the wrong thing. `?` is exactly in line with `iota`, `foo_windows.go`, `flag.Var`, `http.HandleFunc`, etc. I don't know why people respond to literally every Go mistake with 'it's actually not a mistake, you just don't understand the genius', especially since half the mistakes are reverted later and acknowledged as mistakes.
which is _not_ boilerplate, in any sense that would benefit from being mitigated with new syntax or short-cuts.
> Condensing that particular snippet down to `?` would be less terrible than the status quo
This simply isn't any kind of objective or agreed-upon truth. Many people, including myself, believe that the status quo is better than what you're suggesting here.
People who are annoyed with Go at some fundamental level, and who largely don't use the language themselves, delight in characterizing `if` blocks related to errors as "boilerplate" that serves no purpose, and needs to be addressed at a language level.
> `?` is exactly in line with `iota`, `foo_windows.go`, `flag.Var`, `http.HandleFunc`, etc.
I've thought on this at length and I have no clue as to what you think the common property between these things might be. A proposed language sigil that impacts control-flow, an existing keyword that's generally not recommended for use, a build-time filename convention, and two unrelated stdlib type definitions?
You say 'you would never', 'generally not recommended', etc., about things that dominate all code in the wild. Perhaps you do not understand the Go vision. Yes, in both Go and Rust, people should add context to errors; and in both Go and Rust, they don't. Cox's proposal provides something slightly smarter than raw `?`, while Rust was designed smarter from the start and it just takes a library to do that (`snafu` or `anyhow`).
> I've thought on this at length and I have no clue as to what you think the common property between these things might be.
They are examples of the common property I specifically stated in the preceding sentence:
> Go is extremely full of 'helpful' happy-path short functions that leave you reimplementing lots of stuff more verbosely the moment you step off the happy path, inclusive of happy paths that do partially the wrong thing.
(In point of fact you shouldn't use fmt.Errorf if you're serious about errors either; it cannot be usefully inspected at runtime. You want an explicitly declared error type for that.)
I guess this makes it pretty clear that there's no useful conversation to be had with you on this topic.
> (In point of fact you shouldn't use fmt.Errorf if you're serious about errors either; it cannot be usefully inspected at runtime. You want an explicitly declared error type for that.)
You don't need a discrete error type to allow callers to inspect returned errors at runtime -- `fmt.Errorf("annotation: %w", err)` allows callers to check for sentinel errors via `errors.Is` -- which is the overwhelmingly most common case.
Exactly; `return fmt.Errorf("annotation: %w", err)` is just a log-friendlier version of `return err`. The original wrapped error is meant for runtime inspection.
I think we are saying the same thing: it is important metadata which is meant for human consumption (as opposed to the wrapped sentinel error) -- hence "log-friendly".
> `return fmt.Errorf("annotation: %w", err)` is just a log-friendlier version of `return err`
This claim isn't true. The annotation-wrapping isn't just about making the returned error 'log-friendlier', it's annotating the error with context-specific information that's useful beyond "log" contexts. More concretely, that kind of annotation is critical if you expect to be able to introspect runtime errors in any way at all -- it's not a "log-friendlier" version of `return err`, it's an important and necessary change vs. `return err` which is in no way perfunctory or whatever.