4

OK. I know this is a FAQ, and I think the answer is "give up, it doesn't work that way", but I just want to make sure I'm not missing something.

I am still wrapping my head around best practices and rules for use of interfaces. I have code in different packages that I'd prefer to keep decoupled, something like so (doesn't work, or I wouldn't be here):

package A

type Foo struct {}

func (f *Foo) Bars() ([]*Foo, error) {
    foos := make([]*Foo, 0)

    // some loop which appends a bunch of related *Foo to foos
    return foos, nil
}

package B

type Foolike interface {
    Bars() []Foolike
}

func DoSomething(f Foolike) error {
    // blah
}

With this, the compiler complains:

cannot use f (type *A.Foo) as type Foolike in argument to B.DoSomething:
*A.Foo does not implement Foolike (wrong type for Bars method)
    have Bars() ([]*A.Foo, error)
    want Bars() ([]Foolike, error)

Now, I grok that []Foolike is not an interface signature itself; it's the signature for a slice of Foolike interfaces. I think I also grok that the compiler treats []*A.Foo and []Foolike as different things because ... (mumble memory allocation, strict typing mumble).

My question is: Is there a correct way to do what I ultimately want, which is to let B.DoSomething() accept an *A.Foo without having to import A and use *A.Foo in B.DoSomething()'s function signature (or worse, in the interface definition)? I'm not hung up on trying to trick the compiler or get into crazy runtime tricks. I understand that I could probably change the implementation of Foo.Bars() to return []Foolike, but that seems stupid and wrong (Why should A have to know anything about B? That breaks the whole point of decoupling things!).

I guess another option is to remove Bars() as a requirement for implementing the interface and rely on other methods to enforce the requirement. That feels less than ideal, though (what if Bars() is the only exported method?). Edit: No, that won't work because then I can't use Bars() in DoSomething(), because it's not defined in the interface. Sigh.

If I'm just Doing It Wrong™, I'll accept that and figure something else out, but I hope I'm just not getting some aspect of how it's supposed to work.

mjmac
  • 172
  • 5
  • Decoupling is one thing. But when you define your interface recursively, it can only go so far. Then package `A` has to import package `B`. – seong Dec 02 '14 at 17:14
  • 1
    I suggest posting this to the mailing list (https://groups.google.com/forum/#!forum/golang-nuts). The devs frequent there much more often I think. Then you could post their answer here. – korylprince Dec 02 '14 at 17:52
  • 1
    seong's answer of returning making `f.Bars` actually return a `[]Foolike` is the best I see with the problem description we've got--if you can describe the use case (like, what `Foo` and `Bars` really are, etc.) maybe there's a clever way around it involving returning something besides a slice or whatever. But there may not be. – twotwotwo Dec 02 '14 at 18:12
  • It does not feel like you are using the right interfaces. Is seem strange to have to "export" out of the interface some informations like "we are an array of the interface". Something is missing. Can you tell us what are you trying to do ? – fabrizioM Dec 02 '14 at 22:33
  • Also note that packages are not necessarily related to "decoupling". Packages usually represent an "idea" in a very general sense. Depending on your problem your packages `A` and `B` should be in one package. Circular imports or similar dependency issues are usually a hint that you should rethink your package structure. – seong Dec 04 '14 at 11:38
  • Thanks for the responses. In the end, I just decided to use the real type directly and abandoned the interface abstraction. I can't really get more specific about the details of what I'm doing, but imagine that A.Foo is a node of some sort which has methods to return related nodes (ancestors, descendants, etc). I had wanted to use interfaces so that I could mock things in testing and/or swap between implementations of Foo. I have had success in using interfaces for other parts of the project, but seong's point about the recursive definition I think puts the nail in this idea's coffin. – mjmac Dec 04 '14 at 15:13

1 Answers1

1

As the error message says, you can't treat the []FooLike and []*Foo types interchangeably.

For the a []*Foo slice, the backing array will look something like this in memory:

| value1 | value2 | value3 | ... | valueN |

Since we know the values are going to be of type *Foo, they can be stored sequentially in a straight forward manner. In contrast, each element in a []FooLike slice could be of a different type (provided they conform to FooLike). So the backing array would look more like:

| type1 | value1 | type2 | value2 | type3 | value3 | ... | typeN | valueN |

So it isn't possible to do a simple cast between the types: it would be necessary to create a new slice and copy over the values.

So your underlying type will need to return a slice of the interface type for this to work.

James Henstridge
  • 42,244
  • 6
  • 132
  • 114