why this?

371 views
Skip to first unread message

T L

unread,
Sep 28, 2016, 11:25:56 PM9/28/16
to golang-nuts
package main

func main() {
    var m = map[string]int{}
    _, _ = m["abc"] // ok
    _ = m["abc"] // ok
   
    var i interface{} = 789
    _, _ = i.(bool) // ok
    _ = i.(bool) // panic: interface conversion: interface is int, not bool
}

Henrik Johansson

unread,
Sep 29, 2016, 12:32:48 AM9/29/16
to T L, golang-nuts

This is just how type assertion works.
If you don't use the dual return it panics if the actual type is different from the one you try to assert.


--
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...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

andrey mirtchovski

unread,
Sep 29, 2016, 12:52:41 AM9/29/16
to Henrik Johansson, T L, golang-nuts
> If you don't use the dual return it panics if the actual type is different
> from the one you try to assert.

from the language specification:

" x.(T)

asserts that x is not nil and that the value stored in x is of type T.
The notation x.(T) is called a type assertion.
[...]
If the type assertion holds, the value of the expression is the value
stored in x and its type is T. If the type assertion is false, a
run-time panic occurs."

https://golang.org/ref/spec#Type_assertions

the key to why _ = m["abc"] works lies in this text:

"For a of map type M:

x's type must be assignable to the key type of M
[...]
if the map is nil or does not contain such an entry, a[x] is the zero
value for the value type of M"

https://golang.org/ref/spec#Index_expressions

T L

unread,
Sep 29, 2016, 12:53:11 AM9/29/16
to golang-nuts, tapi...@gmail.com


On Thursday, September 29, 2016 at 12:32:48 PM UTC+8, Henrik Johansson wrote:

This is just how type assertion works.
If you don't use the dual return it panics if the actual type is different from the one you try to assert.


but what is the underlining reason for the inconsistency between map index and type assert?
 

Micky

unread,
Sep 29, 2016, 12:56:57 AM9/29/16
to T L, golang-nuts
The reason is directly stated in the Go language spec:


"If the type assertion holds, the value of the expression is the value stored in x and its type is T. If the type assertion is false, a run-time panic occurs."

Here "hold" means if it succeeds.


To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.

T L

unread,
Sep 29, 2016, 1:02:26 AM9/29/16
to golang-nuts, tapi...@gmail.com


On Thursday, September 29, 2016 at 12:56:57 PM UTC+8, Micky wrote:
The reason is directly stated in the Go language spec:

"If the type assertion holds, the value of the expression is the value stored in x and its type is T. If the type assertion is false, a run-time panic occurs."

Here "hold" means if it succeeds.


I know of the syntax in spec.
I just want to understand what is the deep reason for the syntax inconsistency between map index and type assert.
 

andrey mirtchovski

unread,
Sep 29, 2016, 1:07:17 AM9/29/16
to T L, golang-nuts
> I just want to understand what is the deep reason for the syntax
> inconsistency between map index and type assert.

a map is fully typed at compile time. even if its key is of type
interface{} the language defines how to compare two interface{} values
using type assertion here:
https://golang.org/ref/spec#Comparison_operators

"Interface values are comparable. Two interface values are equal if
they have identical dynamic types and equal dynamic values or if both
have value nil."

Using interface{} in assignments on the other hand is only type-safe
at runtime when used with a type assertion.

Henrik Johansson

unread,
Sep 29, 2016, 1:07:42 AM9/29/16
to T L, golang-nuts

I am not sure but perhaps as simple as it is a very natural and known behavior of maps and to make it work syntactically as the type assertion would make it weird.

Ian Lance Taylor

unread,
Sep 29, 2016, 1:31:41 AM9/29/16
to T L, golang-nuts
On Wed, Sep 28, 2016 at 10:02 PM, T L <tapi...@gmail.com> wrote:
>
> On Thursday, September 29, 2016 at 12:56:57 PM UTC+8, Micky wrote:
>>
>> The reason is directly stated in the Go language spec:
>>
>> "If the type assertion holds, the value of the expression is the value
>> stored in x and its type is T. If the type assertion is false, a run-time
>> panic occurs."
>>
>> Here "hold" means if it succeeds.
>>
>
> I know of the syntax in spec.
> I just want to understand what is the deep reason for the syntax
> inconsistency between map index and type assert.

Why do you think there is an inconsistency?

Let me put it another way. A type assertion and a map index are two
different kinds of expressions. They do different things. What kind
of consistency do you expect between them?

Ian

Micky

unread,
Sep 29, 2016, 1:33:40 AM9/29/16
to T L, golang-nuts
TL, the simplest reason is this:

You can live without the "ok idiom" when retrieving a map value but you cannot when type asserting. Think of the consequences for rest of your program, if you forgot to check the status of assertion that is failed (but you didn't know) because the compiler didn't panic! It would be a travesty.

For compiler's perspective, see Andrey's response.

To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
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+unsubscribe@googlegroups.com.

T L

unread,
Sep 29, 2016, 3:04:59 AM9/29/16
to golang-nuts, tapi...@gmail.com


On Thursday, September 29, 2016 at 1:07:42 PM UTC+8, Henrik Johansson wrote:

I am not sure but perhaps as simple as it is a very natural and known behavior of maps and to make it work syntactically as the type assertion would make it weird.



yes.
It is really weird that the behavior of type assertion depends on whether the second OK result value is received or not.
 

T L

unread,
Sep 29, 2016, 3:08:19 AM9/29/16
to golang-nuts, tapi...@gmail.com

I just feel that It is weird that the behavior of type assertion depends on whether the second OK result variable is provided or not.
 

Dave Cheney

unread,
Sep 29, 2016, 3:10:46 AM9/29/16
to golang-nuts
Which of these would you prefer happened instead?

* A failed type assertion always panic'd
* A failed type assertion never panic'd and instead returned the zero value, as the map lookup does
* Something else

Dave

Volker Dobler

unread,
Sep 29, 2016, 3:21:26 AM9/29/16
to golang-nuts, tapi...@gmail.com
Am Donnerstag, 29. September 2016 07:02:26 UTC+2 schrieb T L:
I know of the syntax in spec.
I just want to understand what is the deep reason for the syntax inconsistency between map index and type assert.

"deep reason"s are found in religion and math not in
programming language design.

A type assertion states "this has type x" which is a bold
statement, an assertion and these tend to fail/abort/terminate/throw
if violated in most languages. Also: What could be the possible
return value if the assertion failed? The zero value only.
Now think about how you would use x := foo.(bar)  after a failed
type assertion. Basically impossible, a practically useless language
construct. Which in turn would force to allow the the x, ok = foo.(bar)
only. Now again you do have an "inconsistency" (whatever you mean
with this term) with map access which allows both forms.

Btw: What do you think about the inconsistency between import
statements and for loops :-) ?

V. 


T L

unread,
Sep 29, 2016, 3:22:30 AM9/29/16
to golang-nuts, tapi...@gmail.com


On Thursday, September 29, 2016 at 1:33:40 PM UTC+8, Micky wrote:
TL, the simplest reason is this:

You can live without the "ok idiom" when retrieving a map value but you cannot when type asserting. Think of the consequences for rest of your program, if you forgot to check the status of assertion that is failed (but you didn't know) because the compiler didn't panic! It would be a travesty.

For compiler's perspective, see Andrey's response.

But I don't check the status of an assertion and the the status is false, I surely expect to get a zero value of the wrong type in the assertion.
 

T L

unread,
Sep 29, 2016, 3:23:27 AM9/29/16
to golang-nuts

returned the zero value

T L

unread,
Sep 29, 2016, 3:25:24 AM9/29/16
to golang-nuts, tapi...@gmail.com

Aha, it would be more understandable if you can explain it with code.
:), my English is not very good.
 

Dave Cheney

unread,
Sep 29, 2016, 3:27:52 AM9/29/16
to golang-nuts
That sounds like a very bad idea. You type assert to the wrong type and receive a valid blank value for that type. If you're lucky the program crahesh immedialy because the type's zero value is nil, if you're not the person program continues to execute using this types zero value for an unknown amount of time causing untold data corruption.

This is why go requires you to work hard for this behaviour by making the single value form of the expression the panicing default and the two value form harder to use accidentally.

The same logic I described also applies to map lookup.

T L

unread,
Sep 29, 2016, 3:29:59 AM9/29/16
to golang-nuts, tapi...@gmail.com

If there are any inconsistencies between import statements and for loops, it is very natural for me. :)
 

Dave Cheney

unread,
Sep 29, 2016, 3:31:46 AM9/29/16
to golang-nuts
Sorry, I misspoke, this logic does not apply to map lookup, they are unrelated other than having a one arg and two arg form.

T L

unread,
Sep 29, 2016, 3:36:21 AM9/29/16
to golang-nuts


On Thursday, September 29, 2016 at 3:31:46 PM UTC+8, Dave Cheney wrote:
Sorry, I misspoke, this logic does not apply to map lookup, they are unrelated other than having a one arg and two arg form.

:) yes, that is what the weirdness and inconsistency is.

Dave Cheney

unread,
Sep 29, 2016, 3:37:59 AM9/29/16
to golang-nuts
If you think that's inconsistent, you should see how we used to delete things from maps ;)

T L

unread,
Sep 29, 2016, 3:45:52 AM9/29/16
to golang-nuts


On Thursday, September 29, 2016 at 3:37:59 PM UTC+8, Dave Cheney wrote:
If you think that's inconsistent, you should see how we used to delete things from maps ;)

used to? Any change made for deleting things from maps in history?

Dave Cheney

unread,
Sep 29, 2016, 4:58:35 AM9/29/16
to golang-nuts
It's all in the release history, this change occured before Go 1.0.

Axel Wagner

unread,
Sep 29, 2016, 5:37:05 AM9/29/16
to Dave Cheney, golang-nuts
On Thu, Sep 29, 2016 at 9:31 AM, Dave Cheney <da...@cheney.net> wrote:
Sorry, I misspoke, this logic does not apply to map lookup

But why? It seems to me, everything you said can just as easily be applied to map lookups. Why is a zero-value on a type-assertion potentially dangerous, while it's fine on a map-lookup?
I don't see anything in your E-Mail that is specific to type-assertions (I mean. It's specific to type-assertions as implemented. Just not as theorized).

Indeed, you could argue that, as we deal just fine with the behavior for maps, we would probably deal just fine with the behavior for type assertions. And that the same conveniences we get from *not* panic'ing on map-accesses also would apply to type-assertions.

So… I have to agree, that there is an inconsistency here. I don't think it's a *bad* thing and I prefer my type-assertions panic'ing and my map-accesses not-panic'ing, but I would be hard pressed to give a technical argument why they should be different.

Dave Cheney

unread,
Sep 29, 2016, 5:40:22 AM9/29/16
to Axel Wagner, golang-nuts

A type assertion is dangerous because if the thing you are asserting to doesn't match the type of the value inside the interface, what type should the result be? With the map example the type of the result is known.

Axel Wagner

unread,
Sep 29, 2016, 5:44:59 AM9/29/16
to Dave Cheney, golang-nuts
The thing you are asserting to, obviously? The types are not a problem if you do
v, ok := iface.(someType)
v simply has type someType and will be the zero-value if iface doesn't contain a someType. Why should this be a problem for the non-comma-ok version?
I.e., what would be the technical difficulty in making
v := iface.(someType)
semantically equivalent to
v, _ := iface.(someType)
'cause I don't see any.

Dave Cheney

unread,
Sep 29, 2016, 5:48:46 AM9/29/16
to Axel Wagner, golang-nuts

That's what happens now with the two arg version, the one arg version panics rather than letting you think you got a valid value of the type you expected.

Axel Wagner

unread,
Sep 29, 2016, 6:05:49 AM9/29/16
to Dave Cheney, golang-nuts
I don't understand where we are talking past each other.
I am aware, what the current behavior is. I am (and T L was asking) for technical reasons why a) any justification for the behavior of the type-assertion doesn't apply to map-access and b) any justification for the behavior of the map-access doesn't apply to type-assertions? I don't believe there is any.

On Thu, Sep 29, 2016 at 11:48 AM, Dave Cheney <da...@cheney.net> wrote:

That's what happens now with the two arg version, the one arg version panics rather than letting you think you got a valid value of the type you expected.

Why is that a show-stopper for
v := iface.(someType)
but not for
v := someMap[k]

The value will be of type someType either way, it will be just as valid a value in both cases and the consequences of assuming it's non-zero are just as dire in both cases.

You could argue that assuming that a key is present in a map is just as broken an assumption as assuming that an interface value contains a value of a given type. You could argue that just as
v, _ := someMap[k]
v := someMap[k]
are equivalent,
v, _ := iface.(someType)
v := iface.(someType)
should be equivalent. Or, you could also argue, if you prefer, that just as
v := iface.(someType)
should panic because you shan't just assume that iface contains a someType and continue working with a possibly invalid value, so should
v := someMap[k]
panic, because you shan't just assume that a map contains a given key and continue working with a possibly invalid value. You could argue, that requiring v, _ := iface.(someType) for the non-panicy behavior is better, because it makes explicit that you've thought about it and are aware that iface could possibly be something else, but just as well could you argue that v, _ := someMap[k] for the non-panicy behavior is better for exactly the same reasons.

Again, I'm not really saying it's a *bad* thing that there is an inconsistency here, but I also don't think that it can really be argued that there is much of a technical reason for these two, on the surface very similar constructs, to behave this differently. I believe the true answer is "for historical reasons. No one realized this, when converging on the ,ok construct". Which I think would be a fine answer.

Dave Cheney

unread,
Sep 29, 2016, 6:12:33 AM9/29/16
to Axel Wagner, golang-nuts
Because they are different operations, they just happen to share a similar syntax. 

For example

a, b := 1, 0
c := a - b
d := a / b

One of these generates no result, the other panics.

Following your logic, should division have a two arg form that says if the dividend was zero, and if so would that require all arithmetic operations to support this syntax?

I don't know the historical reason why those two arg forms are like they are, they were there when I got here, and at a time there was a push to add more two arg forms, for division especially, but that didn't pan out.

There is an inconsistency here, but only if you want to assume that maps and interface assertions are the same operation because they share the same syntax.

Konstantin Khomoutov

unread,
Sep 29, 2016, 8:46:14 AM9/29/16
to T L, golang-nuts
"One argument lookup" of a map containing values of type interface{}
produces the zero value for type interface{}. That's because as Andrey
said, such map _itself_ is statically typed: the type of its values is
interface{}.

Conversely, when you type-assert a value of type interface{}, you're
making a claim the _real dynamic run-time_ type of the value held in
that value of type interface{} is such and such.

Thus even though both operations superficially look the same, they're
doing drastically different things, as Andrey and Dave tried to explain.

Here's the code:

package main

import (
"fmt"
)

func main() {
var zvi interface{} // The zero value for type interface{}

m := make(map[string]interface{})

v := m["abc"] // Returns the zero value for type interface{}
fmt.Println(v == zvi) // Prints "true"

v = zvi.(int64) // panics beause zvi contains no dynamic type
// information.
}

Playground link: https://play.golang.org/p/oS7SWHXgJQ

Konstantin Khomoutov

unread,
Sep 29, 2016, 8:47:25 AM9/29/16
to andrey mirtchovski, T L, golang-nuts
Kudos for IMO the best explanation of these matters posted to this
thread ;-)

T L

unread,
Sep 29, 2016, 11:17:30 AM9/29/16
to golang-nuts, tapi...@gmail.com

I'm still not convinced by your explanation.
I never expect the last line should not panic. Type asserting on a nil interface value is expected to panic (it is a surprise that nil map element access doesn't panic).
I just expect type asserting on a non-nil interface value shouldn't panic.

 

Playground link: https://play.golang.org/p/oS7SWHXgJQ

andrey mirtchovski

unread,
Sep 29, 2016, 11:48:34 AM9/29/16
to T L, golang-nuts
> I just expect type asserting on a non-nil interface value shouldn't panic.

of course it should panic if the interface holds an unexpected value
and you're not checking for correctness with the comma-ok pattern.

Go is a statically typed language and by asserting the wrong type on a
variable you have an created an obviously incorrectly typed program.
If the compiler was able to deduce that the interface was holding an
incorrect value at compile time it would never have allowed such a
program to be compiled.

the following two examples both look ridiculous to a Go programmer:

var i int = 42
var b = bool(i)
// b is now false

vs

var i = interface{}(42)
var b = i.(bool)
// b is now false

One shouldn't be able to turn 42 into a "false" in Go. maybe in other
languages, but not Go.

T L

unread,
Sep 29, 2016, 12:38:25 PM9/29/16
to golang-nuts, tapi...@gmail.com


ok, maybe type assertion is really different from map element access, range over slice/map and read from channel.
The later three can be viewed as special form of function callings, but type assertion can't.
 

Konstantin Khomoutov

unread,
Sep 29, 2016, 1:09:49 PM9/29/16
to T L, golang-nuts
I think you still can't "get" the chief distinction: it lies in typing
not in similarities of syntax.

For

m := make(map[string]interface{})

there's a well-defined zero value for the type of values this map holds.

While somewhat dubious, this is useful for maps which merely record the
fact the particular set of keys is "known"; consider:

m := make(map[string]bool)
m["three"] = true

And now:

* m["one"] evaluates to false
* m["two"] evaluates to false
* m["three"] evaluates to true
* m["four"] evaluates to false
...and so on.

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.

That's because type assertions are really used for two cases:

* Conditional switching of the program flow -- such as in "when the
actual type is T do this, otherwise do that".

* Actually _asserting_ the value is of particular type.
To assert something means to verify a particular invariant about how
the world behaves.

To illustrate, you might assert that 4 / 2 == 2, and if this invariant
fails, your runtime (or hardware or whatever) is broken and the only
thing you can sensibly do is to crash your process.

Axel Wagner

unread,
Sep 29, 2016, 1:36:07 PM9/29/16
to Konstantin Khomoutov, T L, golang-nuts
On Thu, Sep 29, 2016 at 7:09 PM, Konstantin Khomoutov <flat...@users.sourceforge.net> wrote:
I think you still can't "get" the chief distinction: it lies in typing
not in similarities of syntax.

For

  m := make(map[string]interface{})

there's a well-defined zero value for the type of values this map holds.

While somewhat dubious, this is useful for maps which merely record the
fact the particular set of keys is "known"; consider:

  m := make(map[string]bool)
  m["three"] = true

And now:

* m["one"] evaluates to false
* m["two"] evaluates to false
* m["three"] evaluates to true
* m["four"] evaluates to false
...and so on.

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

(so, there clearly is no type-problem here, otherwise this wouldn't work or make sense), if you assume that logging to a nil-*Logger would be a nop. If the one-argument form were equivalent, you could do

func Log(ctx context.Context, fmt string, v ...interface{}) {
    ctx.Value(loggerKey).(*Logger).Printf(fmt, v...)
}

Similarly (and closer to your example):

func WithDebug(ctx context.Context) context.Context {
    return context.WithValue(ctx, debugKey, true)
}

func DebugLog(ctx context.Context, fmt.String, v ...interface{}) {
    if ctx.Value(debugKey).(bool) {
        Debug(ctx, fmt, v...)
    }
}

Now, granted, the difference in those particular cases isn't huge, but there are similar cases to be constructed; basically, every time a) the zero value is a useful value (which it should be) and b) you then want to use it in an expression, you now need to put it into a separate variable.

I think the context-example is particularly illustrative, because you are supposed to not depend on a value being present in it and you don't have any other type-information. I'd argue, that it's a sign of idiomatic go code to use v, _ := ctx.Value(key).(someType); making the zero value a useful default and then use that default, if it isn't given otherwise.

Claiming that it would be useless to just use a zero value on a failing type-assertion means contending the notion of zero values being useful things (see also). Which is exactly the same notion that make the zero value a reasonable and good default to return from a map-access.

(that is also the reason why I don't like the implication that a failed type-assertion is something to panic' about. It's based on the assumption that zero values are dangerous, broken or useless)

Henrik Johansson

unread,
Sep 29, 2016, 2:04:30 PM9/29/16
to Axel Wagner, Konstantin Khomoutov, T L, golang-nuts

I don't think it is based on the zero value being useful but a desire to be as exact as possible when working with unknown data. You reach for X when you have Y. Why would you magically get an X back? Panic can arguably be seen as too strong but the multiple return is awesome.


--

Konstantin Khomoutov

unread,
Sep 29, 2016, 3:09:57 PM9/29/16
to Axel Wagner, Konstantin Khomoutov, T L, golang-nuts
On Thu, 29 Sep 2016 19:35:49 +0200
Axel Wagner <axel.wa...@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>.

Ian Lance Taylor

unread,
Sep 29, 2016, 3:12:54 PM9/29/16
to T L, golang-nuts
On Thu, Sep 29, 2016 at 12:08 AM, T L <tapi...@gmail.com> wrote:
>
> On Thursday, September 29, 2016 at 1:31:41 PM UTC+8, Ian Lance Taylor wrote:
>>
>> On Wed, Sep 28, 2016 at 10:02 PM, T L <tapi...@gmail.com> wrote:
>> >
>> > On Thursday, September 29, 2016 at 12:56:57 PM UTC+8, Micky wrote:
>> >>
>> >> The reason is directly stated in the Go language spec:
>> >>
>> >> "If the type assertion holds, the value of the expression is the value
>> >> stored in x and its type is T. If the type assertion is false, a
>> >> run-time
>> >> panic occurs."
>> >>
>> >> Here "hold" means if it succeeds.
>> >>
>> >
>> > I know of the syntax in spec.
>> > I just want to understand what is the deep reason for the syntax
>> > inconsistency between map index and type assert.
>>
>> Why do you think there is an inconsistency?
>>
>> Let me put it another way. A type assertion and a map index are two
>> different kinds of expressions. They do different things. What kind
>> of consistency do you expect between them?
>
>
> I just feel that It is weird that the behavior of type assertion depends on
> whether the second OK result variable is provided or not.

This conversation has reminded me that early in the development of the
language map lookups worked like type assertions: if you wrote m[k],
and k was not present in the map, then you got a run-time panic. In
other words, exactly like when writing v.(T), if v is not of type T,
you get a run-time panic. For both cases, the comma-ok statement let
you check whether the result succeeded without having the panic. So,
back then, the two statements did not have the inconsistency that
concerns you.

The behavior for maps changed with
https://codereview.appspot.com/673042. The discussion that led to the
change was https://groups.google.com/forum/#!msg/golang-nuts/2VQ7h0CMkjg/2qE3rFSfo_4J
.

So, OK, from a certain perspective, there is an inconsistency. And
the reason there is an inconsistency is that it made Go programming
easier to not have maps panic.

But I think type assertion are a very different matter. I don't think
anybody wants to write a Go program that has a type assertion that
silently fails. The only possible argument for that being a good idea
would be to avoid something that looks like it might be an
inconsistency. That is, in my opinion, a terrible argument.

Ian

Axel Wagner

unread,
Sep 29, 2016, 3:47:12 PM9/29/16
to Konstantin Khomoutov, T L, golang-nuts
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.

T L

unread,
Sep 30, 2016, 5:07:39 AM9/30/16
to golang-nuts, tapi...@gmail.com

Ian, great explanation for this question!

On the other hand, Konstantin Khomoutov's explanation really shows the consideration when go team made the syntax decision.
 
Reply all
Reply to author
Forward
0 new messages