You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// We want to work with a set of concrete types, but they all implement an interface:typeObjectinterface {
ID() uint64
}
// There is some off-the-shelf functionality that operates on that interface:funcFinalize(oObject) {
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:typeManageableObject[Uany] interface {
// The (U)nderlying type's pointer method set must implement the Object interfaceObject*U
}
typeManager[TManageableObject[U], Uany] struct { /* ... */ }
// We try something like the following naive code:func (m*Manager[T, U]) Decode(rawstring) {
t:=new(U)
json.NewDecoder(strings.NewReader(raw)).Decode(&t)
Finalize(t) // pass the type as the interfacem.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.
The text was updated successfully, but these errors were encountered:
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.
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.
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
Minimal-ish runnable example: https://go.dev/play/p/ty4Joe4Qyzy
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 onruntime.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 anObject
(which is implicitly valid by the type constraints)This can be worked around by explicitly converting the
t
(which is of type*U
) toT
(i.e.T(t)
) which causes the code to compile.Alternately, if you start out with
var t T
to begin with, you might seeThis 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.
The text was updated successfully, but these errors were encountered: