Lone Learner
Lone Learner

Reputation: 20628

How to deal with duplicate methods in Go interface?

How to deal with duplicate methods in Go interface?

package main

import (
    "fmt"
)

type Person interface {
    Hello()
}

type Joker interface {
    Person
    Joke()
}

type Jumper interface {
    Person
    Jump()
}

type Entertainer interface {
    Joker
    Jumper
}

func main() {
    fmt.Println("hello, world")
}

The following error occurs if I run this code.

$ go run foo.go
# command-line-arguments
./foo.go:24: duplicate method Hello

How to deal with situations like this and how can we avoid duplicate methods in such a scenario?

Upvotes: 9

Views: 6184

Answers (3)

VonC
VonC

Reputation: 1324317

This is followed by "issue 6997, proposal: spec: allow embedding overlapping interfaces".

If you view an interface as a set of constraints on the implementing type, then combining two interfaces (that are not mutually incompatible) such as:

type I interface { f(); String() string }
type J interface { g(); String() string } 

has a natural interpretation that is equivalent to an interface containing the union of such constraints. e.g. these should be equivalent:

type IJ interface { I; J }
type IJ interface { f(); g(); String() string }

but in fact the first is an error: "duplicate method: String".

Go 1.14 (Q1 2020, five years later) might include some improvments:

CL 190258: allow embedding overlapping interfaces , replaced by CL 191257

Quietly drop duplicate methods from embedded interfaces if they have an identical signature to existing methods.

Instead of adjusting the prior syntax-based only method set computation where methods don't have signature information (and thus where de-duplication according to the new rules would have been somewhat tricky to get right), this change completely rewrites interface method set computation, taking a page from the cmd/compiler's implementation.

In a first pass, when type-checking interfaces, explicit methods and embedded interfaces are collected, but the interfaces are not "expanded", that is the final method set computation is done lazily, either when needed for method lookup, or at the end of type-checking.

The spec is updated (CL 190378)

See some examples here.

Upvotes: 2

ij6
ij6

Reputation: 466

The way to do this is to explicitly provide the required methods instead of using the shorthand syntax:

type Entertainer interface {
    Hello()
    Joke()
    Jump()
}

This may seem like code duplication, but note that duplicate code isn't an untypical thing in Go, especially when it leads to clearer code.

Also note this: If you think in terms of typical inheritance in other languages, it may seem like you're losing some information by doing this, because you're not recording the fact that Entertainer inherits from, say, Person. But Go interfaces are purely structural, there is no inheritance. Because an Entertainer has a Hello() method, every Entertainer is automatically a Person, whether or not you explicitly mention Person insided the Entertainer declaration.

All of this compiles without problems (except for a "declared and not used" error) even when you don't use the shorthand syntax for any of the interfaces:

var e Entertainer
var ju Jumper
var jo Joker
var p Person

p = e    // every Entertainer is also a Person
p = ju   // every Jumper is also a Person
p = jo   // every Joker is also a Person

ju = e   // every Entertainer is also a Jumper

jo = e   // every Entertainer is also a Joker

Here's a complete program that compiles and runs just fine. Given these declarations:

package main

import (
    "fmt"
)

type Person interface {
    Hello()
}

type Joker interface {
    Hello()
    Joke()
}

type Jumper interface {
    Hello()
    Jump()
}

type Entertainer interface {
    Hello()
    Joke()
    Jump()
}

let's create a Clown type:

type Clown struct {}

func (c Clown) Hello() {
    fmt.Println("Hello everybody")
}

func (c Clown) Joke() {
    fmt.Println("I'm funny")
}

func (c Clown) Jump() {
    fmt.Println("And up I go")
}

A Clown can greet, jump, and joke, and so it implements all of our interfaces. Given these four functions:

func PersonSayHello(p Person) {
    p.Hello()
}

func JumperJump(j Jumper) {
    j.Jump()
}

func JokerJoke(j Joker) {
    j.Joke()
}

func EntertainerEntertain(e Entertainer) {
    e.Joke()
    e.Jump()
}

you can pass a Clown to any of them:

func main() {
    c := Clown{}

    PersonSayHello(c)
    JokerJoke(c)
    JumperJump(c)
    EntertainerEntertain(c)
}

Here's a link to a Go Playground with the above code.

One final thing – you could argue something like this: "But if I later make a change to Person, it won't be reflected in the other interfaces." It's true, you have to make such an adjustment manually, but the compiler will let you know about it.

If you have this function:

func JumperSayHello(j Jumper) {
    PersonSayHello(j)
}

your code will work without any issues. But if you add another method to Person, code that relies on the fact that a Jumper is a Person will no longer compile. With

type Person interface {
    Hello()
    Think()
}

you get

.\main.go:18: cannot use j (type Jumper) as type Person in argument to PersonSayHello:
        Jumper does not implement Person (missing Think method)

This will be the case as long as you have code anywhere that relies on the fact that a Jumper is always a Person. And if you don't, not even in your tests, then – well, maybe it doesn't actually matter that the jumper doesn't think?

But if for whatever reason you actually need to ensure that a Jumper is always a Person, no matter what changes you make to these interfaces, but this fact isn't actually used anywhere, you can always create code just for this purpose:

package main

type Person interface {
    Hello()
}

type Jumper interface {
    Hello()
    Jump()
}

// this function is never used, it just exists to ensure
// interface compatibility at compile time
func ensureJumperIsPerson(j Jumper) {
    var p Person = j
    _ = p
}

func main() {
}

Upvotes: 14

Navaneeth K N
Navaneeth K N

Reputation: 15501

I don't think it is possible to do this. IMO, interface embedding is just a shorthand for having those functions directly there. So it is equivalent as having two Hello() functions. Hence the error from compiler.

Upvotes: 2

Related Questions