On Thu, Sep 29, 2016 at 9:09 PM, Konstantin Khomoutov < flatw...@users.sourceforge.net> wrote:
> On Thu, 29 Sep 2016 19:35:49 +0200 > Axel Wagner <axel.wagner...@googlemail.com> wrote: > > [...] > > > With type assertions, I really fail to see how returning the zero > > > value for the target type in case the type assertion failed can be > > > useful. > > > > > > > For example: You are using context.Context, save a thing under a > > given key and then provide functions to extract it and do stuff to it > > (say, you have a logger saved in a context). You also make, as a good > > go citizen, the zero value of the thing useful. > > You can still write that, like this: > > > > func Log(ctx context.Context, fmt string, v ...interface{}) { > > l, _ := ctx.Value(loggerKey).(*Logger) > > l.Printf(fmt, v...) > > } > > Henrik tried to highlight a caveat of this approach: > > --------8<-------- > package main > > type Logger struct { > } > > func (l *Logger) Printf(args ...interface{}) { > // Do nothing, intentionally to be OK with nils > } > > func main() { > var n int = 42 > > var wi interface{} = n > > l, _ := wi.(Logger) > l.Printf("whatever") > } > --------8<-------- > > (Playground link: <https://play.golang.org/p/MpZFXvHb3i>). > > This program does not crash, it called a method on a Logger-typed value > which was "automagically produced" from an integer by actively abusing the two-argument form of type assertion. > It's abusing because if you want to call functions on your logger while > not being interested whether it was set or not, you just don't need to > do anything besides type-asserting its container value to be *Logger. > That works because interface{} contains two things: the type and the > value of that type. The latter can perfectly be nil for pointer types. > > To demonstrate: > > --------8<-------- > package main > > import "fmt" > > type Logger struct { > } > > func (l *Logger) Printf(args ...interface{}) { > if l == nil { > fmt.Println("I'm nil") > } else { > fmt.Println("I'm not nil") > } > } > > func main() { > set := &Logger{} > missing := (*Logger)(nil) > > var wi interface{} > > wi = set > log(wi) > > wi = missing > log(wi) > } > > func log(wi interface{}) { > l := wi.(*Logger) > l.Printf() > } > --------8<-------- > > (Playground link: <https://play.golang.org/p/k908qwqad9>). > > As you can see here, we "blindly" type-assert the value to be *Logger > in the log() function using it, and on the first call that > "decapsulates" a non-nil pointer value while on the second call it > fetches a nil value. The point is: in both cases these values have > the type *Logger. > > Maybe you forgot about the distinction between a nil value of interface > type and a nil value _contained_ in an interface value? > I mean <https://golang.org/doc/faq#nil_error>. > No, I didn't (why do people tend to assume that other people don't know the language as well as they do?). Your code would violate (if it actually where similar to mine) a different "rule" for idiomatic use of context.Context, which is that you should not have to assume that a given key exists, i.e. the values saved in a context should be optional. In your case, there needs to be *something* passed to log, which means (if you will) that now your log-function doesn't use the zero value as a useful thing. Or, if you prefer, it implies that I wrap my stuff always and unconditionally in your logging-middleware (which makes it not only potentially annoying, it also adds another indirection in the common case to support an edge-case, because even a nil-value saved in a context does add another link in the linked list that is context.Context). Your code works differently (and is strictly worse, even if not necessarily by much), precisely *because* of that difference you so helpfully pointed out; a typed nil in an interface is still not the zero value. But, again, I also wasn't really trying to argue in favor of either type assertions not panic'ing nor map accesses panic'ing. What I was arguing in favor of is that there is an inconsistency here, for better or for worse. I think I myself believe the way it's now is actively the better way, even *though* it's inconsistent. But the way everyone kept saying "no it isn't", instead of "yes, it is, but it's better this way" seemed kind of weird to me. I find Ian's reply very satisfactory. Thanks for looking up the historical context :) I think these kinds of questions need more replies like that. -- 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. For more options, visit https://groups.google.com/d/optout.