Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spec: can not use instantiated type in type switch, unless constraints are satisfied #64800

Closed
Merovius opened this issue Dec 19, 2023 · 7 comments

Comments

@Merovius
Copy link
Contributor

Merovius commented Dec 19, 2023

Go version

go version go1.21.4 linux/amd64

What operating system and processor architecture are you using (go env)?

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/mero/.cache/go-build'
GOENV='/home/mero/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/mero/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/mero'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/mero/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/home/mero/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.4'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/mero/shared/src/AdventOfCode/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2163326066=/tmp/go-build -gno-record-gcc-switches'

What did you do?

Playground

func main() {
	F[int](X[int]{})
	F[int]("foo")
	F[func()](nil)
}

type X[T comparable] struct{}

func F[T any](v any) {
	_, ok := v.(X[T])
	fmt.Println(ok)
}

What did you expect to see?

The program compiling and running, outputting true, false, false.

We can still meaningfully ask the question if the dynamic type of an interface is X[T], even if T is not comparable. The answer would be false in that case, but that's fine. F might be able to work with any type, but do something more efficient if it is an X[T].

What did you see instead?

./prog.go:9:19: T does not satisfy comparable
@Merovius
Copy link
Contributor Author

FWIW this isn't really a "bug", because it's working as specified. And I didn't want to file it as a proposal, because it obviously intersects with other proposals (like #45380) and I'm not sure this is something we'd want to do in any case.

I just noticed it when trying to implement persistent data structures as interfaces and wanting to special case one implementation that only works with stricter constraints. And thought it might be interesting to keep this in mind as something we might want to allow at some point.

@atdiar
Copy link

atdiar commented Dec 19, 2023

I think it works as intended.
X[T] can only exist if T is at least comparable.

By composition, it means that it should only work if the constraint interface in F is at least comparable (i.e. Implement comparable). Here it's any, so no.

Perhaps that what you want is to be able to assert whether a type T is comparable and deal with being able to then call X[T]?

@Merovius
Copy link
Contributor Author

Type-assertions are about inspecting the dynamic type of an interface. It is explicitly about making a runtime assertion that goes beyond what the static type guarantees.

By analogy with your argument, I could say you shouldn't be able to write any(v).(io.Reader) - because to implement io.Reader, a type has to have a Read method and any doesn't have that, so the type-assertion shouldn't be possible. That's obviously nonsensical logic.

There is no reason why a type-parameter constrained on any can't also satisfy comparable. So there is no reason why that type-assertion would be impossible. So I don't see a reason why I shouldn't be able to type it out.

Perhaps that what you want is to be able to assert whether a type T is comparable and deal with being able to then call X[T]?

That would be one way to enable this (I did mention the connection to #45380). But

func F[T any](v any) {
    switch type T {
    case comparable:
        _, ok := v.(X[T])
    }
}

is still a pretty awkward way to write this.

But to be clear: I was explicitly not suggesting that we do anything concrete about this, in the near future. I'm not sure I think it's a good idea to allow this type-assertion. I just don't see a reason why we can't and it seems like a reasonable thing to want to do.

@zigo101
Copy link

zigo101 commented Dec 19, 2023

It is type assertion related? Same error for the following code:

func F[T any](v any) {
	var _ X[T]
}

@Merovius
Copy link
Contributor Author

I do think that's different. But I can see the point. I did not consider the fact that a type-assertion has to assign the zero value to its left-hand side as well and that zero value can not exist. So yes, I don't think this works after all.

@atdiar
Copy link

atdiar commented Dec 19, 2023

Type-assertions are about inspecting the dynamic type of an interface. It is explicitly about making a runtime assertion that goes beyond what the static type guarantees.

By analogy with your argument, I could say you shouldn't be able to write any(v).(io.Reader) - because to implement io.Reader, a type has to have a Read method and any doesn't have that, so the type-assertion shouldn't be possible. That's obviously nonsensical logic.

I'd agree for interfaces but here T is a type parameter. If the underlying implementation uses an interface type, it's not really one for various reasons.

A more befitting analogy would be to be able to type assert non interface types. That would be checked at compile-time.

A type constraint is about static guarantees.

There is no reason why a type-parameter constrained on any can't also satisfy comparable. So there is no reason why that type-assertion would be impossible. So I don't see a reason why I shouldn't be able to type it out.

You're probably getting confused which happens.
Constraining type parameters wouldn't be possible if it was the case.

On the other hand, a type parameter which satisfies comparable also satisfies any.

Here that's not even sufficient because of composition.
We want every potential type that F accepts to be acceptable for X which means that the outer type constraint should implement the inner one.
any doesn't implement comparable.
(by virtue of type set inclusion)

Imagine that T is a slice of int for instance. How could X[T] even exist?

Perhaps that what you want is to be able to assert whether a type T is comparable and deal with being able to then call X[T]?

That would be one way to enable this (I did mention the connection to #45380). But

func F[T any](v any) {
    switch type T {
    case comparable:
        _, ok := v.(X[T])
    }
}

is still a pretty awkward way to write this.

That's an opinion but if that makes sense, it's not particularly awkward to me. comparable is just an interface after all.

But to be clear: I was explicitly not suggesting that we do anything concrete about this, in the near future. I'm not sure I think it's a good idea to allow this type-assertion. I just don't see a reason why we can't and it seems like a reasonable thing to want to do.

@zigo101
Copy link

zigo101 commented Dec 19, 2023

Maybe what OP needs is like:

func F[T any](v any) {
	switch T := T.(constraint) {
	case comparable:
		_, ok := v.(X[T])
		println(ok)
	}
}

@golang golang locked and limited conversation to collaborators Dec 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants