Generic interface compiles with incorrectly instantiated types

Hey everyone. I have some code that’s compiling and running despite there being a type error. I don’t understand go generics and interfaces that well, so I don’t know if this behavior is intended. I have a few different versions of this issue, but here’s a simple one:

type Wrap[A any] interface {
	isWrap()
}

type Val[A any] struct {
	val A
}

func (v Val[A]) isWrap() {}

func Extract[A any](w Wrap[A]) {
	switch w.(type) {
	case Val[A]:
		// do something
	default:
		panic("shouldn't happen")
	}
}

func main() {
	Extract[string](Val[int]{3})
}

The Extract function takes a Wrap[A] and does a type switch on its implementations. The main function calls Extract with an input Val[int], but it explicitly instantiates the function’s generic to be of type string. So a Val[string] input would be correct, but Val[int] should be a type error. Despite this, the code compiles and panics at runtime, presumably because the case Val[A] expects A to be a string, not an int.

I got some similar examples to compile and run, they all involved instantiating a generic interface with one type but using it as if it had a different type. Is this behavior intended, or maybe is there some implicit unsafe cast happening? Or is this a bug?

1 Like

For the records, the golang-nuts email list has some answers.

TL;DR:

Answer from Ian Lance Taylor:

The Wrap interface ignores its type parameter, so any type that
implements a isWrap method with no arguments and no results will
implement Wrap with any type argument. The argument to Extract is
Wrap[A], so you can pass anything with an isWrap method to Extract.
The type Val[int] does have an isWrap method, so it’s fine to pass
that type to Extract[int].

Brian Chandler explains this further:

Maybe it’s clearer if you simplify Wrap[A] to just Wrap, since they are identical. Go Playground - The Go Programming Language

Then it’s clear that the parameter “w Wrap” to func Extract is just a plain old interface type, unrelated to generics. Dynamically at runtime it can be nil, or a value of any type which implements the Wrap interface.

Interfaces aren’t implemented by structural inheritance, they’re just duck-typing: “does the concrete type implement this method set?” Therefore, these interfaces are identical and interchangeable:

type Wrap1 interface {
    isWrap()
}
type Wrap2 interface {
    isWrap()
}
type Wrap3[A any] interface {
    isWrap()
}

You can pass a value of type Wrap2 to a function which expects type Wrap1. Go Playground - The Go Programming Language

I wonder why the third variant does not trigger an error message like, “Type parameter A declared but not used” or at least a warning.

Maybe some linter will add a respective test.

1 Like