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: spec: allow embedding interfaces with conflicting methods #45405
Comments
I'm not convinced by the case you motivate this with. ISTM that a change in |
Correct: adding a method to an existing, exported interface is a breaking change. Part of the point of this proposal is to contain and isolate the damage from such a break.
Note that my example involves breaking changes in We don't recommend making breaking changes like that, but they can sometimes be useful — especially during early development of a public package, or during ongoing development of packages with limited users (such as private code in use by multiple projects within an enterprise). |
The other place where this may come up is in generic code, in which user-supplied type arguments may cause some generic interface to be unsatisfiable. If the unsatisfiable interface is only used as, say, an input to a method that the user does not intend to call, then the fact that it has no usable implementation is arguably not important. This also parallels some of the discussion in #45346: if we argue that it isn't important to diagnose interfaces whose type-sets are empty due to conflicting concrete-type constraints, then why is it important that we diagnose interfaces whose type-sets are empty due to conflicting method constraints? |
FTR I wasn't arguing that it's "not important", but that the benefits are low compared to the complexity. It is not hard to detect if an interface is not satisfiable, but it's very hard to detect if a type set is empty :) |
You can still do this today without any change to the language, see example below (removed imports so you can directly compile the code):
The new GoLang doesn't need to add complex function/operation overloading, unlike other OO languages, it's focused on basic interface-implementation interactions to simplify APIs. |
@Baha-sk, you can embed conflicting interfaces in a |
That's the whole point @bcmills, when overriding, you're doing it on the Although For the sake of clarity, here's the updated example with embedded interfaces (and their respective implementations): type Foo interface {
fn()
}
type FooBarrer struct{}
func (f *FooBarrer) fn() {
fmt.Println("old fn")
}
type Bar interface {
fn()
}
type BarBarrer struct{}
func (b *BarBarrer) fn() {
fmt.Println("new fn")
}
type FooBar interface {
Foo
Bar
}
func NewFooBarBarrer() FooBar {
return &FooBarBarrer{
Foo: &FooBarrer{},
Bar: &BarBarrer{},
}
}
type FooBarBarrer struct {
Foo
Bar
}
func (e *FooBarBarrer) fn() {
e.FooBar.(*BarBarrer).fn()
}
If say the client has a function referencing |
I think you've missed the point of this proposal entirely, @Baha-sk. I believe this proposal isn't about being stuck on the details of concrete implementations, but rather about changing the language to make code like this compile and run as is. |
Even though It's a mistake to allow swapping interface types in arguments or any field, this will introduce inconsistencies in the language. I think the problem lies in the design of I think as much as we'd like public APIs not to break old clients when migrating, keeping the language clean and consistent is more important. The type struct -> interface relationship is well established and understood. Swapping of interfaces with same function definitions will require additional checks on the interface definition instead of just verifying the type. For me, clarity and consistency overweigh seemless migration of APIs. Don't you agree? That's just my 2 cents, I may be wrong and your case is probably a valid one, the Go Authors/Experts might have a stronger opinion. |
This proposal suggests that we add complexity to the language in order to support a complex code base. While the language should support complex code bases, this seems like an inappropriate approach of adding complexity to the language rather than removing complexity from the code base. Also there doesn't seem to be strong support for this proposal. Therefore, this is a likely decline. Leaving open for four weeks for final comments. |
No further comments. |
I was writing parser for 3rd party schema which might not define fields for types (and even whole types) yet data provider still can send them. Missing types isn't a problem since I could just add them to parent interface and embed generated one inside. However, if there's field in existing type that isn't defined in schema, that leads to situation where I can't safely override interface method for new type that embeds old one. This looks somewhat like this: // generated code
type SomeEvent struct {
EventID string `json:"event_id"`
}
type generatedHandler interface {
SomeEvent(SomeEvent)
}
var generatedDefs = map[string]reflect.Type{
"some_event": reflect.TypeOf(SomeEvent{}),
}
func newEvent(name string) interface{} {
return reflect.New(generatedDefs[name]).Interface()
} // wrapper code
type SomeEventShim struct {
SomeEvent
AnotherField int `json:"another_field"`
}
type Handler interface {
generatedHandler
SomeEvent(SomeEventShim) // I'd like that
}
func init() {
// so I can use that
generatedDefs["some_event"] = reflect.TypeOf(SomeEventShim{})
}
// and that
type HandlerImpl struct {}
func (HandlerImpl) SomeEvent(SomeEventShim) {} |
Background
(This proposal is forked from #45346 (comment).)
Today, the Go compiler rejects programs that declare unsatisfiable interface types — that is, interface types that require the same method name with two different signatures (https://play.golang.org/p/xSzi4tLp4q4).
However, such interfaces can arise in real programs as they evolve over time, due to otherwise-irrelevant incompatible changes in upstream dependencies.
Consider
This package may well continue to compile and work with older versions of
example.com/foo
andexample.com/bar
, but will fail to build when those dependencies are updated, even if the callers have all migrated toNewF
. TheOldF
function can only be removed as a breaking change, which the maintainer of the package might not want to do at this point.However, if the
FooBarBazzer
interface were allowed to decay to an unsatisfiable interface, then the package would continue to compile without error, and only the callers of the deprecatedOldF
function would break when they upgrade to incompatible versions offoo
andbar
.Proposal
I propose that the check for conflicting methods on embedded interfaces be removed.
In order for programs to usefully compile with this restriction removed, we would need a few additional changes. For an interface type
Conflict
that embeds conflicting interfaces:A variable of type
Conflict
may be declared and used. Its only valid value isnil
.nil
interface value).The method set of
Conflict
contains all of the conflicting signatures for the method. Thus, it continues to implement all of the interfaces it embeds, and is assignable to each of those interfaces.The conflicting method definitions can be enumerated by
reflect.Type
andreflect.Value
, but cannot be looked up by name.reflect.Type.NumMethod
andreflect.Value.NumMethod
, each distinct signature for the method is considered to be a different method.reflect.Type.Method
andreflect.Value.Method
enumerate each distinct signature independently.reflect.Type.MethodByName
returnsMethod{}, false
andreflect.Value.MethodByName
returns the zeroValue
for any method with conflicting definitions.Go 2 Language Change Template
nil
values. In this proposal, onlynil
is allowed.reflect
package. Specifically, it is no longer the case that every method returned byMethod
can be retrieved usingMethodByName
.cmd/compile
andgccgo
to accept and propagate methods with conflicting definitions.staticcheck
) to accept and process programs with conflicting methods.MethodByName
methods in thereflect
package may need new conditional branches, but for all existing programs those branches are 100% predictable.)CC @Merovius @ianlancetaylor @griesemer
The text was updated successfully, but these errors were encountered: