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.

Reply via email to