The spec mentions:
Each type T
has an underlying type: If T
is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T
itself.
Otherwise, T
's underlying type is the underlying type of the type to which T
refers in its type declaration.
T2
refers in its type declaration to T1
, which has the underlying type string
.
It is important for the underlying type of T2
to be string
, because it will help for Assignability where
A value x is assignable to a variable of type T ("x is assignable to T")
x's type V and T have identical underlying types and at least one of V or T is not a named type.
This is also detailed in "Golang: Why can I type alias functions and use them without casting?"
When it comes to the underlying type of T4
, we are talking about an underlying unnamed type []T1
.
And again, the assignability rule says you can assign []T1
to T4 (since []T1
is not a named type): its underlying type stops at the first not-named type ([]T1
).
See this example on playground
var t3 T3 = []T1{"a", "b"}
fmt.Println("t3='%+v'", t3)
// var t4 T4 = []string{}
// cannot use []string literal (type []string) as type T4 in assignment
var t4 T4 = T4(t3)
fmt.Println("t4='%+v'", t4)
t4 = []T1{T1("c"), T1("d")}
fmt.Println("t4='%+v'", t4)
Output:
t3='%+v' [a b]
t4='%+v' [a b]
t4='%+v' [c d]
Danny Rivers adds in the comments:
Why is the underlying type of T4
equal to []T1
, not []string
?
The underlying type of T4
is defined to be the underlying of T3
, which is underlying ([]T1
), and since []T1
is a type literal, that is as far down as it goes.
The language of the spec makes it easy to miss that TYPE LITERALS, not just pre-declared types, count as an underlying type.