-
Notifications
You must be signed in to change notification settings - Fork 17.6k
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
Proposal: relaxed rules for assignability with differently-named but identical interfaces #16209
Comments
It would help if you could describe more precisely what you propose to change in the language spec. I think you are proposing that we change the handling of named interface types when such types are used as function arguments or results, or as struct field types, when converting from type to another. Why are interface types special here? In exactly what cases are interface types special? The current rules for type conversions are already a bit complex. Is this change going to make them more complex? That would be a drawback. It should be easy to understand when a type conversion is valid. Anything that makes that harder to understand needs significant benefits. |
I left specific examples in the referenced thread. That was probably a mistake… better for this discussion to be self-contained. Here's a fairly succinct example. We're trying to pass a function as an argument, but the argument types differ because
Honestly, I don't see much practical reason for struct fields to change, but they seem similar enough that it would be confusing for them to be treated differently. Perhaps it would be simpler to limit this proposal to functions: I've done that below in the proposed change. Apologies for the clumsy language: I expect someone with more experience at spec writing can do better. Proposed change:AssignabilityA value x is assignable to a variable of type T ("x is assignable to T") in any of these cases:
|
I think this proposal is more targeted at enforcing this element of the specification:
Because:
If two interfaces types are identical if they have the same methods, then context.Context and x/net/context.Context are identical types, which means that the functions and structures using them should view them as identical types, and therefore type F func(ctx context.Context) Should already be identical types, by the language specification already. |
Nice catch, @puellanivis! That is exactly what this is targeting. So perhaps it's actually a bug. |
Hmm… missed this part “Two named types are identical if their type names originate in the same TypeSpec.” I'm working on a better example that exposes the issue I see at hand. |
It's kind of convoluted, but it is dealing with esoteric one-offs in the language spec vs implementation: https://play.golang.org/p/YlrMrkvY60 First, by spec Instead we get: The proposal here is that But if the first worked, we could just replace all Context functions with literal interfaces… but the later allows us to avoid having to copy-paste types all over the place, when the two interfaces are for all intents and purposes semantically and syntactically identical. (They all accept the same arguments to each method the exact same way, and treat their arguments the exact same way.) This expands the power of interfaces, because interfaces are intended to be semantically flexible, and fulfilled simply by implementing the same receiver methods. |
Interesting, so is the implementation wrong, or the specification? The current spec seems very sensible, so why was it not implemented like that? |
Because this was an esoteric element of the spec that no one has really thought of until now? I mean, who would put a giant interface{ Method1(); Method2() } on their function signatures? I mean, even func (c *C) F(x interface { OUCH! that's ugly! >_< I tried it the other way around, with the interface{} literal type in the interface definition, and B and C take Stringer and fmt.Stringer respectively, but this still didn't work. |
Oh yeah, and by meta-argument, the specification is never wrong, it is the definition. And anything failing to conform to the specification is then out-of-spec. Honestly, I'm not even sure how I would fix the spec to conform to this implementation beyond a really long and verbose exception to checking interface types as identical… |
@puellanivis Regarding your example https://play.golang.org/p/YlrMrkvY60 : Your suggestion to make two interfaces identical if they have identical methods would solve this and the proposal, except for what you also have found already, the fact that the name of the interface types is currently looked at in type identity as well (which is why the above fails). In other words, type identity would have to change such that the type name is not considered for interfaces. That's the simple-most change I can think of, but it's also the most pervasive one in terms of its effect. I don't know what the implications of such a change are. Interfaces are special, and for instance it's not possible to attach methods to interfaces the way it's done for other types; the methods are already part of the type. So the name is not so important. In fact, in most scenarios, the interface name is not important at all. The question is, can we ignore it always? It will permit programs that we cannot write now, including the ones we would like to write and cannot (hence the proposal). But does it also permit programs that we want to prevent from being written? Are there implications for reflection? (quite possibly). I don't know all these answers. One way to make progress would be for somebody to adjust the compiler's identity function for types to use the more relaxed form for interfaces; that should be a pretty straight-forward change I think. And then run all.bash and see what breaks. If nothing breaks there's a reasonably good change it's a backward-compatible language change. At that point we'd have to see how reflect should be adjusted, if at all. If it does, it may or may not violate the Go 1 compatibility guarantee (the behavior of reflect may have changed in incompatible ways). |
AAAAAAAnd… :( you're absolutely right… “… A named and an unnamed type are always different. …” Ugh… so much annoying pedantry to sift through… |
For the most part, I don't see any effect on reflection. You already cannot access the concrete value “inside” the interface. So, you're just left with exported methods, which is currently already identical. I mean, in a real sense, an interface is semantically defined as the set of methods that must be implemented for something to match that interface. So, two interfaces declaring the exact same set of methods are … er… “meta-semantically?” identical interfaces, because they are already identical interfaces by definition. (There is no meaningful way to actually distinguish my Stringer from your Stringer EXCEPT by name.) Structs would still compare types by names if either is named, so as far as structs are concerned at worst, we would allow: Function types would still be different if either is named… so, at worst, we allow And any intra-interface ( o.O … v_v ) assignments would already be allowed and have no chance of panicing due to failure of one to implement the other, because as noted, they must already have identical method implementations. In fact, interfaces ALREADY cannot take named function types. So pretty much the only consequence I can possibly eek out of this mess, is that some unnamed struct types, and some unnamed func types would be able to compare as identical types. The latter of which would allow some named interfaces to compare as identical, which is actually desirable. |
A very related issue is trying to implement a type satisfying |
also see #8082 |
Thanks @metakeule for the reference. I am going to close this proposal since #8082 addresses essentially the same problem and has explored it already a bit more. While not 100% a duplicate, this proposal is trying to address the same problem. |
Indeed, it's pretty much the same bug, and is actually calling for basically the same solution (as I see it). But with 1.7 and the move of context.Context, it has indeed suddenly become much more important than, “it'd be nice if Go2 had…” |
Functions and structures are assignable only when the function arguments or structure fields have identical types. It would be useful to allow assignability in the case where corresponding arguments or fields have identically-shaped interface types.
Passing variables with differently-named but otherwise identical interface types works. However, passing handler functions or structs with such interface types as parameters or fields does not work.
The most important use case is vendoring, where differently-vendored interfaces are common.
Another clear and recent example is renaming: with the moving of
context.Context
into the standard library, it is impossible to pass a handler function with a context parameter, unless both passing and passed-to code rename at the same time.Previous informal discussion: https://groups.google.com/forum/#!topic/golang-dev/lOqzH86yAM4
A note: while it would be possible to actually unify identical interfaces, program-wide, I believe it would cause too many problems when using reflection: it would be unclear which name to use.
The text was updated successfully, but these errors were encountered: