> 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.