-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
Go 2: spec: assignability/conversion to non-interface types annoying to test and/or use #31444
Comments
#19778 and #20621 are a concrete proposal that I believe intends to address many of the same use-cases, although if I understand correctly, neither proposed to add something analogous to a looser type-switch. The other interesting case for assignability vs. type is untyped constants, which receive a default concrete type whenever they are passed as variables. At any rate: as you note, you can use (How often would a feature to address this help in practice, especially given the possibility of generics as an alternative way to spend the feature budget?) |
That said, in general we expect proposals to come with a concrete proposal. (It's fine to solicit alternatives, but if we really have no idea how to address a problem then there's not much point keeping an issue open for it.) So a concrete proposal would be really helpful here, even if it has known flaws. |
I was sort of secretly hoping that the answer was "haha there's already a perfectly good solution, why don't you..." and I just missed it. Hmm. Okay, though, here's at least an easy way to express the thing: Assignable/Convertable Type Switches and Assertions A type assertion may take the additional forms Similarly, the types used in the cases of a type switch may be prefixed with one or two ~ characters, indicating approximation. Thus:
This would also work the other way. Thus, if you had a plugin.Lookup value of type I think ~ is currently impossible here lexically so it doesn't break existing code, assignability and convertability tests are an established thing... |
Looking at this... I'm not sure whether this is enough clearer or more expressive than the reflect operations to justify a language change. The comparison is basically:
vs
If there's a way to get rid of the need for the separate |
Generally the pattern I've seen is to pull the var desiredType = reflect.TypeOf(*new(DesiredType))
[…]
if reflect.TypeOf(fn).AssignableTo(desiredType) {
[…]
} |
Hmm. Yeah, that seems a bit nicer. I think it's still necessary to have the pointer object around to use |
The idea of loosened type asertions and switches with Something like this: package main
import (
"fmt"
)
type X int
type FuncPtrX = func() *X
func A() *X {
return nil
}
func isItFuncPtrX(in interface{}) {
if _, ok := in.(FuncPtrX); !ok {
fmt.Printf("not a FuncPtrX: %T\n", in)
} else {
fmt.Printf("isItFuncPtrX OK\n")
}
}
func isItFuncX(in interface{}) {
if _, ok := in.(func() *X); !ok {
fmt.Printf("not a func() *X: %T\n", in)
} else {
fmt.Printf("isItFuncX OK\n")
}
}
func main() {
isItFuncPtrX(A)
isItFuncPtrX(func() *X { return nil })
isItFuncX(A)
isItFuncX(func() *X { return nil })
} |
Ooh, that hadn't occurred to me. Yes, I probably could. It's slightly less useful in the non-function case, where I want to distinguish things actually of the type from merely-same-underlying-type, but it seems like it'd work. I think this is a combination of two minor warts; one is that you can't give a function a defined type, the other is that it's occasionally desireable to have a way to do assignability/conversion tests for things other than interfaces. And this particular use case just happens to overlap them. If functions could have defined types, I'd probably never have noticed the assignability-test question, and if the assignability-test existed, I probably wouldn't have really thought about the defined types. |
This overlaps a bit with #30931, which is a proposal for allowing defined/named types for functions. |
Thanks. I think you're right that the most annoying case arises when converting to function types, and it seems that using an alias addresses that case. Other than for function types I think it's rare to want to ask whether an interface type is assignable or convertible to some other type, and the reflect package is available for cases where that is important. So this doesn't seem to justify adding a new language feature. |
What version of Go are you using (
go version
)?1.12, but it doesn't really matter.
Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?N/A
What did you do?
Originally, was looking at
pkg/plugin
, but the underlying issue applies more broadly. The context was creating a defined type for functions, then declaring functions that match its signature, and checking whether symbols returned byp.Lookup()
are of the type. Of course, they can't be -- no function ever has a defined type, it always has an underlying type. A variable of function type can have a defined type, of course, but variables and functions behave a bit differently in plugin code (a lookup of a non-function produces a pointer-to-object, a lookup of a function gives you a function-typed thing).If you type-assert or type-switch a thing to a target type which is an interface type, the test performed is basically an assignability test on the underlying type and the target type. This allows you to determine not whether the existing object was "really" of that type, but whether it could be. For a non-interface target type, though, you can't do that; you can only check whether that is the specific underlying type.
Related code:
https://play.golang.org/p/qMxLfYzUgpc
(This seems surprising at first, but it's because
int
is a defined type, but function types aren't.)The problem comes when trying to use something like plugin with, say, a defined API. The API might want to define a function type, say,
api.FooFunc
. I load a function symbol from a package, and I want to know whether it's aFooFunc
. But I can't check that, because it can't ever actually have that type -- it's going to have whatever underlying type the API used. So I can do a type assertion on that literal type, but that means that I'm duplicating the type, so if the API wants to change the definition of the type (and plugins get updated), my code that's checking whether functions are of that type also has to change. Even if it's in the API, it has do be a duplicate of the type definition; I can't refer to the type definition directly.Except, perhaps, by doing fancy things with
reflect
, and usingAssignableTo
orConvertableTo
.On the other hand, it's also clear that in many cases, we want that distinction between types to be firmly protected. For instance, if we have
type Miles int; type Kilometers int
, we don't want a type switch to think that Miles and Kilometers are interchangeable, or that either is interchangeable with int. This may be adequately addressed by the assignability qualifiers, and the basic types being considered defined types.What did you expect to see?
I'm honestly not sure, but the more I look at this the more it feels sort of weird that you need chains of function calls and tests using
reflect
to implement "if X could have been assigned to type Y, give me a type Y with X's value" for non-interface types, but for interface types, it's justy, ok := x.(Y)
.I can't immediately see a good fix for this. It's very hard to write generically with the tools available, and I don't think it'd be a good idea to change the existing semantics for type assertions, but it might be nice to have some way to express a different kind of type assertion that can handle assignability.
This may not matter for non-functions, because non-functions can always be declared with the right type anyway. The issue comes from the distinction between
func x() {}
andvar x func() = func() {}
, which express substantially different things; there's no way to declare a function of a defined function type, as opposed to a variable holding a function-reference. (I suppose that would another way to solve this, but I don't have any good syntax ideas.)What did you see instead?
An example someone from #darkarts suggested:
https://play.golang.org/p/QmTPtwwizW5
This seems like it's close to the shortest form (and it would need to be longer to avoid panics if the symbol wasn't the right type).
The text was updated successfully, but these errors were encountered: