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

Cannot use pointer to generic type as interface without explicit conversion #52507

Closed
kylelemons opened this issue Apr 22, 2022 · 2 comments
Closed

Comments

@kylelemons
Copy link
Contributor

What version of Go are you using (go version)?

$ go version
go version go1.18 darwin/arm64

Does this issue reproduce with the latest release?

Yes

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

go env Output
$ go env
GO111MODULE="on"
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/kyle.lemons/Library/Caches/go-build"
GOENV="/Users/kyle.lemons/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/kyle.lemons/go/pkg/mod"

GOOS="darwin"
GOPATH="/Users/kyle.lemons/go"

GOPROXY="https://proxy.golang.org,direct"
GOROOT="/opt/homebrew/Cellar/go/1.18/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/opt/homebrew/Cellar/go/1.18/libexec/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.18"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"

GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/t8/cjh0whg91cz4xv7fqwhx374r0000gp/T/go-build2552819107=/tmp/go-build -gno-record-gcc-switches -fno-common"
GOROOT/bin/go version: go version go1.18 darwin/arm64
GOROOT/bin/go tool compile -V: compile version go1.18
uname -v: Darwin Kernel Version 21.4.0: Fri Mar 18 00:46:32 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T6000
ProductName:	macOS
ProductVersion:	12.3.1
BuildVersion:	21E258
lldb --version: lldb-1316.0.9.41
Apple Swift version 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)

What did you do?

Minimal-ish runnable example: https://go.dev/play/p/ty4Joe4Qyzy

// We want to work with a set of concrete types, but they all implement an interface:
type Object interface {
	ID() uint64
}

// There is some off-the-shelf functionality that operates on that interface:
func Finalize(o Object) {
	fmt.Printf("Finalizing %T\n", o)
}

// We're trying to provide a type-safe abstraction that can be used for any of the
// set of concrete types that can be passed to the above functionality:
type ManageableObject[U any] interface {
	// The (U)nderlying type's pointer method set must implement the Object interface
	Object
	*U
}
type Manager[T ManageableObject[U], U any] struct { /* ... */ }

// We try something like the following naive code:
func (m *Manager[T, U]) Decode(raw string) {
	t := new(U)
	json.NewDecoder(strings.NewReader(raw)).Decode(&t)
	Finalize(t) // pass the type as the interface
	m.Objects[t.ID()] = t // call one of the interface methods on the type
}

This came up while working on building abstractions around the Kubernetes runtime.Object interface to provide type-safety for implementing Kubernetes controllers. This requires having concrete types for creating, decoding, type asserting, and for dealing with the kubernetes client libraries, which tend to rely on runtime.Object heavily.

What did you expect to see?

I expect the above "naive" approach to compile.

What did you see instead?

The compiler must be told to check that these operations are allowed, but it does seem to allow them if you do. See the example for two ways that the compiler can be "hinted" to allow this. At a high level:

If you try to pass new(U) as an Object (which is implicitly valid by the type constraints)

cannot use t (variable of type *U) as type Object in argument to Finalize:
        *U does not implement Object (type *U is pointer to type parameter, not type parameter)

This can be worked around by explicitly converting the t (which is of type *U) to T (i.e. T(t)) which causes the code to compile.

Alternately, if you start out with var t T to begin with, you might see

cannot use t (variable of type T constrained by ManageableObject[U]) as type *U in assignment:
       T does not implement *U (type *U is pointer to interface, not interface)

This can also be worked around by converting t to *U (i.e. (*U)(t)) which causes the code to compile.

So, it seems like the compiler knows that these operations are safe, but has to be explicitly told to check for it.

@ianlancetaylor
Copy link
Member

The constraint on U is any, so a value of type *U doesn't have any methods, so *U does not implement Object.

The constraint on T is ManagableObject[U], and *U is embedded in that type, so it's OK to convert the type *U to the type T. T also embeds Object, so any type argument that satisfies T must implement Object, so the conversion to Object is OK.

So this seems to be working as expected.

Yes, the compiler could deduce from the fact that some type argument satisfied T that *U must have an ID method. But nothing says that explicitly, and it's not a trivial deduction. Requiring the explicit conversion seems reasonable.

Closing because I think this is working as expected. Please comment if you disagree.

@kylelemons
Copy link
Contributor Author

kylelemons commented Apr 22, 2022

Ah, ok so playing around with it a bit more I do agree.

The thing that I was missing, I think, is that when I say T(new(U)) that is actually adding information that then lets the compiler deduce that the implicit conversion to Object is allowed. Similarly to how you can http.HandlerFunc(f) to "give" it the method, but can't pass f directly as an http.Handler. If I just do Finalize(Object(t)) that also doesn't work, so it's not that I'm hinting to the compiler to do the interface check, it's that t actually doesn't implement the interface (according to the type system) until I convert it to T.

Thanks.

@golang golang locked and limited conversation to collaborators Apr 23, 2023
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

3 participants