Repository navigation
proposal: spec: represent interfaces by their definition and not by package and name #8082
Description
Activity
Comment 3 by toddw@google.com:
While admittedly this issue comes up rarely in regular code, as a data point, I run into this issue quite a bit. I've built an RPC stub compiler that takes an API specified in my own language, compiles it, and then generates RPC stubs in different languages. Go is the core generated language. As a simplified example, let's say the user tells me to generate stubs for a RPC function "Print" that streams int32 from client to server, and streams strings from the server back to the client. Here's the Go API that I generate for the user on the client side. type PrintClientStream interface { // SendStream returns the send side of the client stream. SendStream() interface { Send(item int32) error // Places an item on the output stream Close() error // Indicates no more items will be sent } // RecvStream returns the receiver side of the client stream. RecvStream() interface { Advance() bool // Stages an item to be retrieved via Value Value() string // Returns the staged item Err() error // Returns errors } } Obviously the choice of "int32" and "string" are up to the user, as is the RPC function name. The reason SendStream() and RecvStream() return anonymous interfaces is because of this issue. A client may need to use two different services that define different APIs, but result in the same streaming types; client sends int32 and receives string. I'd like them to be able to write a single function that operates over the two different PrintClientStream interfaces. But if I named the return types from SendStream and RecvStream, they'd lose this ability. I also have a straw man proposal. I understand the concepts of type identity and assignability as defined in the language spec: http://golang.org/ref/spec#Properties_of_types_and_values I also appreciate the consistency of the current type identity rules wrt named types: a named and an unnamed type are always different. Whenever you see a "type" declaration in a source file, you know that a distinct type is being defined. Let's say we changed the language spec to say that type identity for interfaces ignores the name. I.e. interface names are simply a convenient shorthand, but for type identity purposes, it's as if every interface were anonymous. The rules for comparing these interfaces remains the same; it's an order-agnostic comparison of method names and their function types, and unexported methods in different packages are always different. I think this is a nice and consistent way to specify this feature. The main downside I see is that it's yet another special-case in the language spec. But interfaces are already special; we already have the special-case in our value assignability rules. <wishful thinking> If you haven't tuned out so far, here's a thought. Why wait until Go2 to incorporate this feature? My rationale: it seems unlikely that anyone was actually relying on the existing semantics. After all the compiler has ensured that all static type checks obey the existing rules, and we're simply relaxing those rules so that more cases are allowed. But for full disclosure, I do recognize that if we change this in Go1, existing code can notice a change in behavior. After all we provide type assertions in the language, as well as the reflect package. E.g. http://play.golang.org/p/OscKQCdDOL That's where my wishful thinking comes in. I can't think of a reasonable way someone would be using the existing semantics; my assertion is that if they're relying on something this subtle, their code deserves to be broken (and I'm only half-kidding). But I can see that if we strictly follow the Go 1 compatibility rules, we simply cannot change this feature, regardless of how much the code that relies on this deserves to be broken. Thoughts?- addedv2An incompatible library changeAn incompatible library changeLanguageChangeSuggested changes to the Go languageSuggested changes to the Go language
on Nov 8, 2014 This reminds me of the strawman to add typed objects to JavaScript, which considers two struct types equivalent iff they have the same fields (name and type) in the same order.
Reacted by Matt JoinerI'm thinking main.Writer should be treated as different as io.Writer. Because WriterTo have different signature of argument. I prefer to use embed interface of io.Writer in this case.
@mattn I want to be able to write a function that can receive any of the WriterTo interfaces.
A quick note on this issue for anyone that is still watching it: Go "does the right thing" if the 2 inner interfaces are declared anonymous. That means the compiler and runtime already support the ability to do the right thing, it's just being overly strict if the interface has a name. Seems that a simple change, to ignore any given name when type checking 2 interfaces, would easily solve this issue.
Reacted by Brad Peabody, Matt Joiner and J. Emrys Landivar67 remaining items
Right, I don't mean to digress. My point is that there are several possible solutions to the problem I have, and this proposal is one of them.
(Accepting mutually-recursive aliases would be another. Allowing aliases of struct types to define methods would be a third, although that seems less likely to happen.)
When it comes to recursive interface definitions, I would propose, that
type A interface { SomeThing(A) }and
type B interface { SomeThing(B) }would result in the same (nameless) internal representation.
Let's go exponential?
@metakeule The internal representation doesn't have to be the same, or nameless; the implementation may choose whatever representation is suitable. The point of this issue is that the rules for identity for interfaces would have to be changed in the spec.
@griesemer Yes, you are right.
I chose "internal representation" as a helper to illustrate the point, that when checking for identity there would be some representation that is not part of the language but has some meaning or checks for "self-ness" if that is a word; an argument that is the surrounding interface itself would not be considered the interface name, but this special placeholder "self".
I'd still love for this to be fixed for Go 2!
This proposal seems to be missing the
Proposallabel. Is that intentional?I came across this problem recently when I am trying to implement marshaling/unmarshaling objects to/from key-value stores. I think solution to the original problem in comment 1 would be good to have in Go 2. Below is my interface design for marshaling objects into key-value stores. Any other solutions to my problem are also much appreciated. Thanks.
The Problem - Cyclic Dependencies between interfaces wouldn't compile.
type Encoder interface { Encode(name string, object KVMarshaler) error } type KVMarshaler interface { MarshalKV(enc Encoder) (key string, value []byte, status error) }My Solution 1 - Break the cycle using expanded anonymous interface.
type Encoder interface { Encode(name string, object interface{MarshalKV(Encoder)(string,[]byte,error)}) error } type KVMarshaler interface { MarshalKV(enc Encoder) (key string, value []byte, status error) }Problem 2 with Solution 1 - Following TreeEncoder doesn't compile because
Encodesignature doesn't satisfy the Encoder interface with the anonymous interface.type TreeEncoder struct { ... } func (te *TreeEncoder) Encode(name string, object Marshaler) error { ... }Solution for Problem 2 - This works. Since number of encoder types would be far fewer than number of object types that must implement marshaling methods, I am going with this for now.
type TreeEncoder struct { ... } func (te *TreeEncoder) Encode(name string, object interface{MarshalKV(string,Encoder)(string,[]byte,error)}) error { ... }- removedNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
on May 12, 2020 - addedLanguageChangeReviewDiscussed by language change review committeeDiscussed by language change review committeeand removedv2An incompatible library changeAn incompatible library change
on Aug 6, 2024
An interface is a collection of method names and signatures. Say, we have: package a type Writer interface { Write([]byte) (int, error) } type WriterTo interface { WriteTo(Writer) (int64, error) } type WriterTo2 interface { WriteTo(w interface{Write([]byte) (int, error)} (int64, error) } From the perspective of the one writing an implementation, the a.Writer and io.Writer are the same: As long as the Write method has the right signature they are both fulfilled. However a.WriterTo and io.WriterTo are not, simply because they refer to Writers of different packages although they have the same definition. Even WriterTo2 would only be compatible with io.WriterTo if they later had an anonymous interface too. see http://play.golang.org/p/kOH11TOlsE Since interfaces are all about reusable code this behaviour must be considered an artifact of the way, interfaces are represented and treated during compilation. Since interfaces have no methods and no values, their only value is to help the programmers via the type constraints. From the implementation perspective a.WriterTo, io.WriterTo and a.WriterTo2 must be treated as being the same, a contraint to have a method WriteTo(w interface{Write([]byte) (int, error)} (int64, error). The names (and packages) of the interfaces should not be relevant for the compiler. Instead the definition should be the representation and the names just shortcuts to refer to them.