Skip to content

proposal: spec: represent interfaces by their definition and not by package and name #8082

Description

@metakeule
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.

Activity

  1. ianlancetaylor commented on May 22, 2014

    @ianlancetaylor
    Contributor

    Comment 1:

    Labels changed: added go2, languagechange, repo-main, release-none.

    Status changed to LongTerm.

  2. griesemer commented on Sep 10, 2014

    @griesemer
    Contributor

    Comment 2:

    See also issue #8691.

    Status changed to Thinking.

  3. gopherbot commented on Nov 8, 2014

    @gopherbot
    Contributor

    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?
  4. maxnordlund commented on Feb 18, 2015

    @maxnordlund

    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.

  5. mattn commented on Feb 19, 2015

    @mattn
    Member

    I'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.

    http://play.golang.org/p/IklkWaIvWG

  6. added this to the Unplanned milestone on Apr 10, 2015
  7. metakeule commented on Apr 28, 2015

    @metakeule
    Author

    @mattn I want to be able to write a function that can receive any of the WriterTo interfaces.

  8. evanphx commented on Jul 16, 2015

    @evanphx
    Contributor

    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.

  9. 67 remaining items

  10. bcmills commented on Dec 14, 2017

    @bcmills
    Contributor

    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.)

  11. metakeule commented on Jan 2, 2018

    @metakeule
    Author

    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.

  12. cznic commented on Jan 2, 2018

    @cznic
    Contributor

    Let's go exponential?

  13. griesemer commented on Jan 2, 2018

    @griesemer
    Contributor

    @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.

  14. metakeule commented on Jan 9, 2018

    @metakeule
    Author

    @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".

  15. evanphx commented on Sep 13, 2018

    @evanphx
    Contributor

    I'd still love for this to be fixed for Go 2!

  16. DeedleFake commented on Sep 13, 2018

    @DeedleFake

    This proposal seems to be missing the Proposal label. Is that intentional?

  17. bvk commented on Jan 28, 2019

    @bvk

    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 Encode signature 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 {
    ...
    }
    
  18. removed
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on May 12, 2020
  19. added
    LanguageChangeReviewDiscussed by language change review committee
    and removed
    v2An incompatible library change
    on Aug 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions