> And I agree that the above function is much easier to read and much 
faster to write than the first version! But now I'm raising unchecked 
exceptions instead of handling errors properly.

However you're not "raising an unchecked exception"; you're panicking, 
which is something different. Go does not encourage this at all.  It would 
be fine to write it that way if calling isGreen on a missing or non-color 
key should never happen - i.e. you know that the programmer seriously 
screwed up if it ever got this far.  But it would almost never be 
appropriate to attempt to recover() from such a situation.

panic/recover has two main uses:
1. something very serious went wrong. Either it was a programmer error or 
an internal runtime error; either way, data structures are likely in an 
inconsistent state and it's better to crash than to attempt to carry on.
2. some *very* limited specific use cases which require being able to 
unwind a deep call chain.

Regarding your sample code: it's OK but I can think of alternative APIs, 
depending on how isGreen is intended to be used.

1. The user is allowed to call isGreen on something which may or may not 
exist, or may or may not be a color, and they just want to know if it's a 
green thing or not a green thing.  In that case, return only a bool.

func isGreen(key string) bool {
    record := m[key]   // aside: no need for 2-arg form, missing map key 
gives zero value
    if record == nil {
        return false
    }
    if !record.IsActive() {
        return false
    }
    cRecord, ok := record.(ColorRecord)
    if !ok {
        return false
    }
    return cRecord.Color() == colors.GREEN
}

It's a little verbose, but the intent is absolutely clear, and IMO much 
more obvious that your brief version.  The brief version does raise the 
question "what happens if I call isGreen in situation X, Y or Z"?  The 
answer is "you must never call isGreen in those situations".

2. The user calls isGreen but also wants to know if it was a color thing or 
not.  Then you could return (bool green, bool ok), in the same way to the 
two-argument forms of map lookup and type assertions.

3. The user wants to know exactly *why* it wasn't a color thing.  Then you 
can make it similar to your original code, but return an error which is 
from a set of constants, rather than fmt.Errorf - which is only useful for 
printing and not much more.  That's closest to properly "checked" 
exceptions.

On Saturday, 22 October 2022 at 22:25:16 UTC+2 dple...@google.com wrote:

> I understand what you are saying.  Yes, any function can call panic.
>> Yes, Go also has run-time panics such as division by zero.  Yes, there
>> is code that explicitly uses panic and recover to simplify error
>> handling (you don't have to refer to external packages, there is code
>> like that in the standard library, such as encoding/gob).
>>
>> However, I think my broader point still holds despite those facts.
>>
>
> The part I don't understand is that Go straight-up encourages having a 
> panic instead of handling exceptions in certain specific cases. Suppose I 
> have a map m from string ids to some Record interface, and some of those 
> records satisfy a ColorRecord interface. Then I can write a function that 
> handles exceptions properly like:
>
> func isGreen(key string) (bool, error) {
>     record, ok := m[key]
>     if !ok || record == nil{
>         return false, fmt.Errorf("no record with key %q", key)
>     }
>     if !record.IsActive() {
>         return false, nil
>     }
>     cRecord, ok := record.(ColorRecord)
>     if !ok {
>         return false, fmt.Errorf("record with key %q is not a 
> ColorRecord", key)
>     }
>     return cRecord.Color() == colors.GREEN
> }
>
> Or, I can streamline this considerably by ignoring the second return 
> arguments from type assertions and map lookups, which means if anything 
> goes wrong it'll panic:
>
> func isGreen(key string) bool {
>     r := m[key].(ColorRecord)
>     return r.IsActive() && r.Color() == colors.GREEN
> }
>
> And I agree that the above function is much easier to read and much faster 
> to write than the first version! But now I'm raising unchecked exceptions 
> instead of handling errors properly.
>
> The ability to ignore the second return argument is obviously not a 
> standard feature of expressions in go - it's a special case that's 
> hard-coded into the language for a few specific expressions, and as I 
> understand it exists specifically to avoid having to check errors in these 
> expressions. It seems to me that the existence of this language feature 
> proves that there genuinely are cases where most people agree that 
> streamlining exception handling is not just worthwhile, but in fact so 
> useful that we're ok adding special cases to the language itself AND 
> sacrificing compiler-enforced error handling entirely to achieve it.
>
> How does this align with the philosophy that expressions shouldn't ever 
> affect control flow? Or do opponents of control-flow expressions also 
> believe that one should never fetch from a map or do a type assertion 
> without checking ok, even if you know it should be impossible for it to 
> fail?
>
> 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/409b8dc1-445d-4cc5-bd86-720880f66d0cn%40googlegroups.com.

Reply via email to