Resolving type parameter ambiguities

August 2, 2023

First today, one rather mundane sentence from the spec…

Type parameter declarations

Just as each ordinary function parameter has a parameter type, each type parameter has a corresponding (meta-)type which is called its type constraint.

Followed by a bit of WTF…

A parsing ambiguity arises when the type parameter list for a generic type declares a single type parameter P with a constraint C such that the text P C forms a valid expression:

type T[P *C] …
type T[P (C)] …
type T[P *C|Q] …
…

If you’re up for it, I’ll try to break this thing down. If you’re not up for it, no worries. See you tomorrow! 👋😆

Let’s consider the first example provided: type T[P *C] …. How can this be ambiguous?

Well, it looks like an array type declaration, where the array length is derrived from a constant expression, such as in this example:

const P = 3
const C = 4

type T [P * C]int // T is of type [12]int

Other expressions are possible, too, which is what leads to this possible ambiguity.

So, we need a way to solve this WTF scenario, and Go gives us two of them…

In these rare cases, the type parameter list is indistinguishable from an expression and the type declaration is parsed as an array type declaration. To resolve the ambiguity, embed the constraint in an interface or use a trailing comma:

type T[P interface{*C}] …
type T[P *C,] …

So either wrap your constraint in interface{…}, or add a magical comma at the end. Poof! problem solved.

But still. WTF?!

Quotes from The Go Programming Language Specification Version of December 15, 2022


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Type switches with generics

More live coding today! Join me! And bring your questions. Type switches … A type parameter or a generic type may be used as a type in a case. If upon instantiation that type turns out to duplicate another entry in the switch, the first matching case is chosen. func f[P any](x any) int { switch x.(type) { case P: return 0 case string: return 1 case []P: return 2 case []byte: return 3 default: return 4 } } var v1 = f[string]("foo") // v1 == 0 var v2 = f[byte]([]byte{}) // v2 == 2 The example included is nice, because it shows two examples of the type parameter P being used: Both the first case (case P) and third (case []P) use the type parameter.


Type switching on a non-interface value. Sorta.

Type switches … Cases then match actual types T against the dynamic type of the expression x. As with type assertions, x must be of interface type, but not a type parameter, and each non-interface type T listed in a case must implement the type of x. The types listed in the cases of a type switch must all be different. TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .


Switch expressions

Expression switches … If the switch expression evaluates to an untyped constant, it is first implicitly converted to its default type. The predeclared untyped value nil cannot be used as a switch expression. The switch expression type must be comparable. In this paragraph, “switch expression” refers to the expression that optionally comes after the switch keyword, as in: switch len(x) { So first off, if it’s a untyped constant, it’s first converted to its default type.

Get daily content like this in your inbox!

Subscribe