The primary benefit that structural interfaces give you, IMO, is less typing. Which has plenty of value, don't get me wrong; it's awesome that Go has successfully reduced the amount of syntactic overhead needed to implement interfaces. But I think the benefits of it shouldn't be overstated.
The problem goes away with less ambiguous naming conventions (e.g. DrawGun() and DrawPicture()), but overloading is so gosh darn convenient, and thinking of unambiguous names is quite difficult. C# is really the only mainstream language that gets this right with explicit interface implementations.
In fact it's probably not that far away from where Go is now. Go already has something very similar for implicit composition / mixins. Now, if you do something like this:
type Foo struct {
Bar
}
Then Foo automatically has the methods of Bar, e.g. Foo.bar() instead of Foo.Bar.bar() although the former is really just shorthand for the latter.If you then have:
type Foo struct {
Bar
Baz
}
Then assuming that Baz also has the method bar() then you now need to be explicit about it and call Foo.Bar.bar() or Foo.Baz.bar() because the compiler doesn't know which one Foo.bar() means.Of course, this is slightly different to interface disambiguation, but there's no reason why you couldn't have two function definitions with the same name and some added interface qualifier - the functions would then only be callable if the object is cast to an interface. In Go, casting to an interface is a bit like boxing, as it creates a separate vtable for that object's methods to match the interface ABI.
Hence, you could have say:
a := Foo(foo)
b := Bar(foo)
Now a has a vtable entry for Foo::foo() -> a.foo() and b has a vtable entry for Bar::foo() -> b.foo().On the other hand, there would be no foo.foo() method unless one were declared separately without any interface qualifier.
In languages that place more emphasis on type deduction and composition it's much more likely you're calling draw to tell it what to draw on, and the aggregate type information once you introduce an argument or two is actually quite rich.
Which leaves you with things like Close(), which, let's be honest, we don't need 30 interfaces that all just have Close in them just because the base language didn't happen to include one. The concept is simple and relatively unambiguous and almost always has to do with some kind of disposal of resource.
You'd never do this:
cb := cowboy.New()
screen.Paint(cb)
Anyone who wrote that code would have to be insane, and anyone reviewing the code would tell the person they were insane.Also, would you not test your code? Like, at least run it and make sure it doesn't do crazy stuff? Maybe write some unit tests?
There can always be edge cases where functions don't work precisely as you expect, but that happens without interfaces, too. Pass an array into a sorting function and it turns out to sort by string length not alphabetically.... the programmer bears some small responsibility for actually understanding what the functions that he calls actually do.
For instance, in JDBC there is a PooledConnection.close() method and a Connection.close() method. Both Connection and PooledConnection are interfaces. They are semantically related but it's not a polymorphic relationship. PooledConnection does not extend Connection.
PooledConnection.close() must always close the actual physical connection to the database because PooledConnection is used by the connection pool itself. The Connection interface is used by the application and hence Connection.close() may close the connection or return it to a connection pool.
JDBC drivers usually come with implementations of both interfaces where the Connection implementation wraps an instance of a PooledConnection implementation. Arguably, being able to formally declare which interface a particular close method belongs to is beneficial in cases like this.
Or if your refactoring and decide to split an interface so what was X is now Y and Z. Consider adding unkillable NPC's to a game. Rather than having special code to handle MOB's that you can't kill you remove HP's from the MOB interface and add a killable interface and now the type system helps you refactor your code by complaining when you try to harm something without HP's. At the same time you don't have to change any existing objects.
1. A and B were defined in separate libraries. In this case, the arguments about the likelihood of two types happening to define two methods with the same name and signature apply equally well to the likelihood of the two interfaces having a common subset at all.
2. A and B were defined in the same library. Here, I suspect most library authors in a language with explicit interfaces would notice this and factor out the common methods in A and B into a separate interface that A and B derive from. You might argue that the library writer could forget to do this, but I think it's not much more probable than the scenario in which A and B have no structural subset because the common functionality has a different names, or has a different return type, or has the arguments in a different order.
You might have Name and Location where Location is (latitude longitude altitude). Now you want to map things, while existing objects Trucks with Name and Location are easy you want also map stuff with a name and address. You can get a latitude and longitude from an address, but not altitude and adding meaningless altitude is IMO a bug waiting to happen.
Ok like we have Close(). One could have Commit(), or MtxDeterminant() or Launch() things like that, where just a lots of people using same terminology in a domain are just bound to create collisions (on individual names!). Now high likely it is that a combination of those would be hit, not very sure on that.
I've used it around a Stats interface which required a single method: Statistics() map[string]string and have been able to hook up otherwise independent code into a common logger. Speaking of loggers, the fact that the built-in log package doesn't expose an interface has always been an annoyance to me specifically because I can't build my own code and know that it'll satisfy a log.Logger interface in other projects.
Don't forget "with the same semantics".