6

In this code snippet from the spec

type T1 string
type T2 T1
type T3 []T1
type T4 T3

The spec says:

The underlying type of string, T1, and T2 is string.
The underlying type of []T1, T3, and T4 is []T1.

Why is the underlying type of T2 not T1, but string?
Shouldn't the underlying type of T4 instead be []string and not []T1 if the underlying type of T1 is string?
Confused.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
nhooyr
  • 1,104
  • 1
  • 13
  • 31

1 Answers1

6

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.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • I am so lost I created a generic with a slice constraint but when I pass a slice it does implement it why? – Agyakwalf May 25 '22 at 04:42
  • @Agyakwalf Could you show your code in a separate question (with a link back to this one)? that way, others can comment on it. – VonC May 25 '22 at 06:01
  • I am ok ,now ... I think I am getting the hang of the generics – Agyakwalf Jun 16 '22 at 19:09
  • @Agyakwalf Great, well done. Good reads on this topic: [Revisiting arrays and slices with generics](https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/revisiting-arrays-and-slices-with-generics), [A generic approach to check if an element is present in an array/slice](https://betterprogramming.pub/how-to-write-generic-helper-functions-with-go-d47c52986016), [slice of bytes](https://twitter.com/go100and1/status/1536892875450880000), ... 1/2 – VonC Jun 16 '22 at 19:13
  • @Agyakwalf 2/2 [Why slice doens't work more nicely with Generics?](https://www.reddit.com/r/golang/comments/s9pf2w/why_slice_doenst_work_more_nicely_with_generics/), [generic slice examples](https://gosamples.dev/tags/generics-intro/). – VonC Jun 16 '22 at 19:14
  • Nice explanation. Once I realised that in `Each type T has an underlying type: If T is one of the predeclared boolean, numeric, or string types, or a type literal`, type literal is []T1, `[]int`, `map[string]int` I understood what an underlying type. Also I'm guessing that `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.` is true because these types are primitive types? – bit Sep 07 '22 at 17:59
  • 1
    @bit Yes. You also find them presented in [this section](https://kuree.gitbooks.io/the-go-programming-language-report/content/3/text.html). – VonC Sep 07 '22 at 18:05
  • @VonC thanks. Is a pointer part of these types whose underlying type is itself? – bit Sep 07 '22 at 19:50
  • @bit You can declare a [pointer to an underlying type](https://go101.org/article/pointer.html): `*int // An unnamed pointer type whose base type is int.`. More on [underlying types here](https://go101.org/article/type-system-overview.html#underlying-type). – VonC Sep 07 '22 at 20:18
  • @VonC thanks for the links. Are the two websites you linked trustworthy sources for becoming an expert in Go? – bit Sep 07 '22 at 20:55
  • @bit Mostly the second one, managed by [Tapir Liui](https://twitter.com/TapirLiu/) is a great resource. Its [twitter account](https://twitter.com/go100and1) is very active. – VonC Sep 07 '22 at 21:55
  • I was still confused after reading both question & answer, with this question: Why is the underlying type of T4 equal to []T1, not []string? I figured it out: The underlying type of T4 is defined to be the underlying of T3, which is underlying ([]T1), and since []T1 is a type literal, thats as far down as it goes. The language of the spec makes it easy to miss that TYPE LITERALS, not just predeclared types, count as an underlying type. – Danny Rivers Dec 12 '22 at 19:38
  • @DannyRivers Thank you for this feedback. I have included your comment in the answer for more visibility. – VonC Dec 12 '22 at 21:58
  • Can we say underlying type of "type xyz struct { var str string }" is string – Tarun Aug 15 '23 at 13:58
  • 1
    @Tarun No, the underlying type of `xyz` is the entire `struct` type literal. The field within the struct `var` is of type `string`, but that doesn't mean the underlying type of the entire struct `xyz` is `string`. The underlying type is the entire structure, not just one of its fields. – VonC Aug 15 '23 at 14:03