ISTM that a lot of your arguments boil down to throwing two disparate
things into the same pot:
1. Detecting and handling failure modes outside the current process, which
have to be expected and should be dealt with gracefully by a correct
program.
2. Limitations of Go's type system, which result in bugs, which are
detected at runtime, which imply that the program is fundamentally
incorrect and can't be relied on.
They are fundamentally different things. Languages with exceptions tend to
use the same (or similar) mechanisms for both, but Go is not one of that.

In Go, the first is an `error`, the second a `panic`. In Java, the first is
an Exception and the second a RuntimeException.

Talking about "checked" vs. "unchecked exception" for the difference
between `return err` and `panic` is IMO misunderstanding the nomenclature.
The difference between those two is "no exception" vs. "exception". The
difference between "checked and unchecked exceptions" is between returning
`error` and returning `*os.PathError`, for example. I would say Go uses
"checked" anything, if the compiler would force a function to declare which
error types it can return and then force the caller to type-switch on those
types.

On Mon, Oct 24, 2022 at 6:31 AM 'Daniel Lepage' via golang-nuts <
golang-nuts@googlegroups.com> wrote:

> In practice, though, Go code *does* have a lot of unchecked exception
> usage - every time someone ignores the 'ok' on a type assertion, calls
> panic() or t.Fatal() from a helper function instead of returning an error,
> or writes a MustFoo() version of some function that panics instead of
> returning an error, that's a point where they *could* be using checked
> exceptions (i.e. return values), and have chosen not to because the code is
> easier to write.
>

I am one of the most adamant supporters of gratuitous usage of `panic` I
know of and I almost never do it to make code easier to write. It is always
because 1. I know that a correct program should fulfill some invariant, 2.
the Go type system can't express that, so 3. a panic is the next best thing.
That's even true for the template `Must` functions. In an ideal world, the
compiler would already verify that the template strings are well-formed
(similar to `go vet` does for `Printf`). But it can't, so we have to delay
that type-check to runtime.

Panicing in those situations is the right thing to do, because that code
shouldn't have run in the first place. Pretending that incorrect code can
police itself by returning an error or somesuch is, in my opinion,
delusional. It just leads to an exponentially exploding space of incorrect
states the program could be in and the programmer must consider. And to
*even buggier and less stable* code, because it's impossible to reason
about it.

Writing v.(T) isn't "lazy error handling". It's "I'm restricting the
state-space of the program to those executions in which v has dynamic value
T - I wish the compiler had done it for me".


>
> Regarding your sample code: it's OK but I can think of alternative APIs,
>> depending on how isGreen is intended to be used.
>>
>
> I said earlier that I didn't want to get bogged down on specific examples,
> and this is what I meant - I know that there absolutely are better ways to
> implement the specific example I gave, I was just trying to demonstrate
> that Go not only already allows "expressions that change control flow", but
> in fact has explicit special cases in the language in order to make it
> possible. The "isGreen" example was just to demonstrate this - I know that
> this particular example could be reimplemented to sidestep the problem, but
> I've seen code where this wasn't true (I didn't include any here because
> the examples I can think of are difficult to understand without reading the
> whole of a library first, and I obviously don't expect anyone to do that
> just so that I can make an example).
>
> Reflecting on this more, I think there are several distinct points that
> I'm arguing here. I'll try to make them explicit as I think them through:
>
> *1.* *There are places where streamlining error handling is acceptable
> and encouraged.*
>
> I don't mean that it's *always* good, or even "usually acceptable", just
> that cases do exist where it's desirable.
>
> The evidence for this is strong:
>  * As I mentioned earlier, Go has special case syntax for certain
> operations like type assertions explicitly to streamline error handling. If
> streamlining errors was never a good idea, then Go would require you to
> write f, ok := x.(Foo) even if you know for a fact that x cannot be
> anything but a Foo, the way it currently requires you to write b, err :=
> Bar(x) even if you know for a fact that x is a valid argument for Bar that
> won't cause an error.
>  * In many cases, including in the standard libraries, there are functions
> that return errors and then accompanying functions with names like
> MustFoo() that just call Foo() and panic if there's an error. This is also
> error streamlining, and like with type assertions the streamlining comes at
> the cost of using panics. Checking sourcegraph.com suggests that hundreds
> of Go projects already choose to make this tradeoff:
> https://sourcegraph.com/search?q=context:global+lang:go++/func%5Cs%28%5C%28%5B%5E%29%5D%2B%5C%29%5Cs%29%3FMust/&patternType=standard
>  (yes,
> some fraction of these are exclusively for module-level initialization, but
> at least half of the handful I randomly sampled were being called in other
> functions).
> * The Ondatra library I linked earlier
> <https://github.com/openconfig/ondatra/blob/main/dut.go#L113>, as well as
> other test helper libraries I've seen, use t.Fatal in the same way - they
> judged that "if got, want := ondatra.DUT(t,
> "name").Config().System().Hostname().Get(t), "localhost"; got != want {
> t.Errorf(...) }", which Fatals if there is no "name" entry in the DUT
> table, if the attempt to fetch the hostname fails, or if the hostname is
> unset, was still preferable to making people write out the fatal checks
> themselves every single time, even though it means that if you don't
> actually want this to terminate your test you have to workaround it with
> panics and recovers
> <https://github.com/openconfig/testt/blob/main/testt.go#L44>.
>
> *2.* *Streamlining doesn't have to mean using unchecked exceptions like
> panic() or t.Fatal().*
>
> All of the dozens of error handling proposals I've read so far are
> fundamentally aimed at letting you use the return value of a function that
> might return an error in contexts other than just assignment, and between
> them there are lots of different examples of errors that are more
> streamlined but still must be handled. My proposal is an example of one way
> to do this, but it is not the only way.
>
> *3. Streamlining shouldn't only apply to error handling that terminates
> the function.*
>
> Unlike panics, errors are values, and should be treated as such, which
> means that the calling function should be able to decide what to do, and
> this should include continuing. Frequently it won't - a lot of error
> handling is just return fmt.Errorf("more context %w", err) - but any
> proposal that assumes that it *always* won't is, IMO, confusing errors with
> panics. This is the question that first started this thread - I didn't
> understand why all the existing error proposals explicitly required that a
> function terminate when it encounters an error, and AFAICT the answer is
> "because people are used to thinking of errors more like panics than like
> return values".
>
> *4. The best way to have people write high-quality code is to make it
> easier to write high-quality code.*
>
> Nobody should ever have to look at their code and think "My API would be
> easier to use in normal circumstances if I just panicked in abnormal
> ones..." and then have to decide whether it's more important that their
> callers have a streamlined API or a principled one.
> This is not hypothetical - I talked to some of the engineers behind the
> Ondatra project that I linked above, and they went through exactly this,
> where they wanted to return errors but their users all felt it made the
> tests too verbose, and that's why they use t.Fatal() everywhere.
>
>
>
> I didn't start this thread with the intent of creating a new error
> handling proposal - I just wanted to understand why all the existing
> proposals don't care about point 3. But the more I think about this, the
> more I'm surprised it hasn't already been proposed.
>
> Is part of the problem that the discussion around the
> try/check/handle/etc. proposals got so involved that nobody wants to even
> consider anything that looks too similar to those? Would it be more
> palatable if I proposed it with names that made it clearer that this is
> about the consolidation of error handling rather than an attempt to replace
> it entirely?
>
> onErrors {
>     if must Foo(must Bar(), must Baz()) > 1 {
>       ...
>    }
> } on err {
>    ...
> }
>
> Thanks,
> Dan
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/CAAViQtiXBHsVg4r-aTWR1mQWDX_7eRfhtPOKZ_YaNiP2ai7iSw%40mail.gmail.com
> <https://groups.google.com/d/msgid/golang-nuts/CAAViQtiXBHsVg4r-aTWR1mQWDX_7eRfhtPOKZ_YaNiP2ai7iSw%40mail.gmail.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAEkBMfGAQ1Jxzv94XVrKip_VyCn-gNmFDqGZxpgnZm-jK-aqVw%40mail.gmail.com.

Reply via email to