Skip to content

Go 2: spec: assignability/conversion to non-interface types annoying to test and/or use #31444

@seebs

Description

@seebs

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 by p.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 a FooFunc. 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 using AssignableTo or ConvertableTo.

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 just y, 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() {} and var 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeLanguageChangeSuggested changes to the Go languagev2An incompatible library change

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions