Type Assertion Question

I’m trying to understand a type assertion detail. The following:

type StringThing string
var a interface{} = StringThing("test")
x, ok := a.(StringThing)
fmt.Println(x, ok)	// "test true"
y, ok := a.(string)
fmt.Println(y, ok)	// " false"

and:

type ArrayThing []interface{}
var a interface{} = ArrayThing{"one",2}
x, ok := a.(ArrayThing)
fmt.Println(x, ok)	// "[one 2] true"
y, ok := a.([]interface{})
fmt.Println(y, ok)	// "[] false"

demonstrate the issue.

I’m not sure why the type assertions don’t match the underlying type definitions in these cases. I’m not arguing that there is incorrect behavior here, I just want to know the reasoning.

My current thinking is that these are type assertions, not type conversions and the specific type declarations are creating new, separate types, not macros for the underlying types. Thus the second assertion in each case fails.

That being said, the point of the assertion would seem to be that the compiler can assume something about the value (e.g. it is possible to iterate though an array of interface{} in each case) and in both cases the assumption would seem to be valid.

The Go compiler implements the language specification, not your thinking.

The Go Programming Language Specification

Type declarations

Type definitions

A type definition creates a new, distinct type with the same underlying type and operations as the given type, and binds an identifier to it.

TypeDef = identifier Type .

The new type is called a defined type. It is different from any other type, including the type it is created from.

Type assertions

For an expression x of interface type and a type T, the primary expression

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.

More precisely, if T is not an interface type, x.(T) asserts that the dynamic type of x is identical to the type T.

Type identity

Two types are either identical or different.

A defined type is always different from any other type.

StringThing and string are distinct, different types. They are not identical.

The following:

type StringThing = string
var a interface{} = StringThing("test")
x, ok := a.(StringThing)
fmt.Println(x, ok)	// "test true"
y, ok := a.(string)
fmt.Println(y, ok)	// "test true"

and

type ArrayThing = []interface{}
var a interface{} = ArrayThing{"one",2}
x, ok := a.(ArrayThing)
fmt.Println(x, ok)	// "[one 2] true"
y, ok := a.([]interface{})
fmt.Println(y, ok)	// "[one 2] true"

work as I originally expected.

Using the equals sign in the type declaration does not create a new distinct type.

Once again, the Go compiler implements the language specification.

The Go Programming Language Specification

Type declarations

A type declaration binds an identifier, the type name, to a type. Type declarations come in two forms: alias declarations and type definitions.

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .

Alias declarations

An alias declaration binds an identifier to the given type.

AliasDecl = identifier "=" Type .

Within the scope of the identifier, it serves as an alias for the type.

You changed from your type declaration from a type definition

type StringThing string

to an alias declaration

type StringThing = string

For the alias declaration, StringThing and string denote identical types.


A type alias declaration enables gradual code repair during large-scale refactorings: Proposal: Type Aliases.

Google covariant and invariant types.

interface{} is covariant

array types are invariant

This is a pretty dense subject of set theory and gets theory thick really quickly.