
Go Interfaces, the Tricky Parts - signa11
https://timr.co/go-interfaces-the-tricky-parts
======
tsimionescu
It always bothers me that, when discussing why `[]struct` can't be passed to a
function expecting `[]interface{}` in Go, people always give the explanation
that it would require extra memory allocation and so on. If it were only that,
this would be a fixable problem.

What you should instead mention in these cases is that it would be
fundamentally type unsafe to do this. Slices are mutable, so `[]struct` does
not fulfill the contract for `[]interface{} `, since I can store any Go type
in an `[]interface`{} , but not in an `[]struct`. The same is true for
pointers.

Note: if this were only a problem of memory layout, the designers of Go could
have built into the language a special type that wraps a slice of structs and
behaves like a slice of interfaces, by doing automatic wrapping/unwrapping
when you read from/write to an index. This would obviously complicate the
runtime, so it may not be desirable anyway, but it could be done and it would
solve the problem discussed in the article. In contrast, there is no way to
solve the fundemtal type mismatch, so I see it as a much better explanation.

~~~
mkozlows
Yeah, I kept waiting for the article to mention covariance and contravariance,
and it never did. A very weird explanation of a not-that-weird phenomenon; I
suspect the author didn't really understand what's going on.

~~~
bogomipz
Might you or someone else have some links you could share that talk about
covariance and contravariance?

~~~
pulisse
Eli Bendersky has a good post[1] on the topic.

[1] [https://eli.thegreenplace.net/2018/covariance-and-
contravari...](https://eli.thegreenplace.net/2018/covariance-and-
contravariance-in-subtyping/)

~~~
bogomipz
Oh this is great read, thanks!

------
voidhorse
Nice article. I’m still getting used to go so it’s good to have this resorce!
I find the go tour a bit lacking on details on the subject of interfaces. Go’s
approach to interfaces is also one of the language’s breaks from convention
that confuses me. The implict implementation approach, imo, actually hurts
ubderstanding when reading code instead of helping. I _sort of_ get the idea
that using the implicit approach enables greater decoupling between interfaces
and implementors, but it also does a poor job at signaling intent. it’s kind
of nice to be able to start at the top of some type/class/whatever declaration
and see that the author inteds it to implement the methods defined by foo
interface, vs having to read the individual func defs, parse the somewhat
syntactically odd func (T) arguments to comprehend that the function refers to
this type (defined elsewhere) and implements this interface method (defined
elsewhere). But it may be totally fine once I get used to it.

It’s almost like using a language that _really_ wants to be only imperative
but begrudgingly introduces support for minimal oop. In spite of this though,
it’s still a pretty great language and I have a lot of fun reading and using
it. Some of it’s other patterns and decisisons, such as the v, ok :=
expresions I find quite delightful to read and use in practice.

~~~
inyorgroove
I've notice this problem as well, makes it really difficult to search what all
"classes" implement a certain interface. Not sure how wide spread it is, I see
this hack some times:

var _ MyInterface = (*MyImplementation)(nil)

~~~
rqk9j
You might be defining interfaces on the wrong end. Interfaces should be
defined where they are used, so searching for implementations becomes a non-
issue anyway.

[https://github.com/golang/go/wiki/CodeReviewComments#interfa...](https://github.com/golang/go/wiki/CodeReviewComments#interfaces)

~~~
tsimionescu
The problem of finding implementations of an interface is harder if they live
in separate packages, isn't it? Why would it be a non-issue?

I'm not saying that we should define them together, the article makes sense in
its recommendation. I'm just saying that the GP also seems correct to me,
there is a problem in finding interface implementations in Go that would be
lessened by explicit implementation (though I hope go pls will fix this on the
tooling side).

~~~
grey-area
I’ve never seen this problem, when did it come up?

As OP says usually the consumer defines an interface, so they aren’t ‘used’ in
lots of places, and are rarely changed independent of the code that uses them
(which could easily use its own interface instead if required). Typically the
implementer changes (which doesn’t matter as long as it still conforms), not
the interface.

The exception would be interfaces provided in the std lib, like io.Reader,
which are widely used elsewhere but those won’t change at this point.

~~~
tsimionescu
The way I see it, you normally have 3 places where an interface is 'used':

1\. there is code written to depend on the interface

2\. there is code written to satisfy the interface

3\. there is code written to pass some implementation to a function using the
interface

The recommendation, which I agree with, is to define the interface in area 1,
which is always a specific package. Areas 2 and 3 can be spread all over the
code, in different places. io.Reader is a good example - there is a single io
package, and most code which does something with an io.Reader is there.
Implementations exist in many places, some in the standard library, some in
your own code base. Then, all over your code base, you may have code which
takes some specific implementation of an io.Reader and calls some function in
io and passes that specific Reader to it. This shows that even if you respect
the recommendation in the code, you can still end up with many places which
'use' an interface in some sense.

Now, to give a more specific use case, say I have defined an interface which
takes a slice, but I expect that the slice is used in a read-only manner. I
want to audit all existing implementations to ensure that they respect these
semantics, and I can only do that by hand since I can't express this in Go's
type system.

Another simpler example is that I know what function I want to call, and what
interface it takes, and now I want to find out what implementations exist for
that interface, to see if I can re-use one of them or if I need to create a
new implementation.

~~~
grey-area
I'd contend that code in places 2 and 3 shouldn't care about the interface
except insofar as they violate it (for which they'll get a compile error and
be able to fix the problem).

The only place that really cares about the interface is place 1 - where it is
used, where it defines a contract for the types passed to a function which
they must conform to.

Re your examples, if you want a slice to be used in a read-only manner, pass
in a copy of the slice, it's the only way to be sure in future too - those
use-sites might be modified later anyway so one check doesn't really help.
Interfaces are not intended to limit this sort of use, nor are they a good
mechanism to do so. Re the existing implementation, I can't imagine losing
track of types such that they'd be a good fit for a given problem, but I
wouldn't know about them, interfaces are usually small so it's just a question
of one or two functions...

While it's of academic interest to see which types conform to which interface
(and people have written little tools to do this), I don't find it's really a
limitation in real-world go code, it just doesn't really come up, and I like
that interfaces are explicitly one-way, you don't declare them on the
implementation side for good reasons.

~~~
tsimionescu
In my own real world code, this problem comes up daily or more - I have a
concrete class, but is it through an interface for mocking purposes. When I am
trying to follow the code which uses the interface, I want to follow what
galena to the values passed into the interface, through the real (or sometimes
mock) implementation. Hopefully gopls will some day be able to do this, but it
is a constant annoyance at the moment that I have to break my flow and search
for the implementation class by hand.

I am honestly considering just getting rid of the interface and finding some
other hackish way to mock the code, just because of the improvement in ease of
following the code.

------
leshow
An article about covariance/contravariance that doesn't once mention either
terms...

------
nemothekid
> _It was also very different from languages like TypeScript and Java, where
> if User implemented Named, as well as being able to pass User to a method
> accepting Named:_

This is possible in Java? Is this a new feature? Or is this only true for
arrays (rather than List)?

I could have sworn List<Cat> would be incompatible with List<Animal>.

~~~
tsimionescu
In Java, Cat[] can be passed to a function expecting Animal[]. List<Cat> can't
be passed to someone expecting List<Animal>.

The first case is allowed by the language, but it is not type-safe - depending
on your usage, you may get type errors at runtime (trying to write a Dog to an
Animal[] that is actually a Cat[]).

Java does also support a safe way of achieving variance - your function can
take a List<? extends Animal>, in which case you can pass a List<Cat> to it.
The compiler also makes sure that a function that takes a List<? extends
Animal> can't write a Dog to that list, so this is actually type safe.

------
marcrosoft
> Does this Go snippet compile, and if not, why?

Line 2: `User` is undefined... can't read the rest of the article.

