45 comments

[ 2.8 ms ] story [ 97.8 ms ] thread
> Does this Go snippet compile, and if not, why?

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

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.

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)

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

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

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.

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.

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.

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.

But again, it's easier to solve your problem if you state it differently. Why do you want to know who implements an interface?

In a statically compiled binary, the compiler will complain at every conversion if the interface is not satisfied.

The compiler will only complain if the intended semantics are fully expressed in the type system, which would be difficult to do in Haskell or Idris, and is completely impossible in Go. If I maintain both the consumer and the producer of the interface, it is my job to find all implementations of that interface and ensure that they respect the intended semantics.
>Interfaces should be defined where they are used

Generic interfaces (e.g. Reader) could be used in 20000 different places, packages, repos, etc...

my Go code is littered with these statements solely to make it explicit that I intend to implement that interface
What's the point? You want to use those structs, isn't it? Wherever you pass the struct and it won't satisfy interface you'll know anyway.
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.

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.
To be fair to the author of the article, the official Go blog, written by Go's designers, offers the same explanation as the article, unfortunately.
There was a proposal https://github.com/golang/go/issues/7512 to make interface method return types covariant, but it didn't go anywhere, and people seemed to favor the broken behavior (returning a concrete type can fail to satisfy an interface, because the crude matching doesn't care that the type has all the expected methods).
My favorite comment on this topic is from Tim Sweeney: http://lambda-the-ultimate.org/node/735#comment-6678

Going after two folks in Java and C# communities (the later being involved in developing the language) for committing the same error.

Note that in the meantime, C# has gotten this pretty right, with IEnumerable (a read-only list) and support for co- and contra-variance at type declaration time.

Hopefully, some day, Go will also get this 'obscure' feature, though I'm not holding my breath. I would be extremely surprised if their initial version of generics will support anything like this.

I wonder if this could be caught differently. Such as say, allowing implicitly []struct to be passed where a []interface{} is required, but only if said receiver makes no modification. Otherwise, it's a compile time error. Of course, a read only slice marker would work as well.
This one of my top Go complaints as well (and I love the language).

If Foo implements interface Bar (Baz() error, Moo() string), then I should be able to pass in []Foo to an argument expecting []Bar, because the former is a slice of a type Bar. The compiler should be a good compiler and do the needful to manage args.

All I can expect do on the called side is iterate through the slice, and call the interface function on each element. That sounds contractual to me :-).

As I explained, that is wrong. You would need a different type, let's call it read-only-slice,to support that.

To make the problem more explicit, let's say you have

    func baz(bars []Bar) {
        bars[0] = nil
    }

    type Foo struct {}

    baz(make([]Foo, 5))
In this case, the program would fail at runtime, as you can't store a nil in a slice of Foos. The baz() function is perfectly correct in itself, if you passed an array of Bars it would work perfectly.
I get that part. I want the language/compiler to allow me to use it where it makes sense (as an argument) and prohibit doing things to it that don't (as you demonstrated).
That would be nice in principle, but where do you expect the error to be thrown in the example above?

I don't know of any language which tries to automatically deduce whether a variable is used in a read-only manner or not, and base covariance on this deduction.

Even C++, which allows you to explicitly annotate an array to declare that it is read-only (const vector<base*> ) doesn't allow you to pass an array of derived elements instead, so I assume there is some difficulty in implementing these checks and ensuring correctness.

The "solution" is to accept an `interface{}` and pass in a `[]struct`!

"When writing generic Go, only accept `interface{}` and never a slice" is a painful maxim but works.

Well, that would allow you to call the function, but now you can't write the body of the function without reflection: you can't iterate over an interface{}, and you can't cast a []struct (edit) wrapped in an interface{} (/edit) to a []SomeInterface, so you're back to square one.
Yes, but now you no longer need to change the function declaration and all callsites (major version change) to support new types (if they're slices or not), just need to implement more reflection or typecasting cases within the function (minor version change).
Counterpoint: if you modify the call site to pass in a new value, you don't know whether the function will accept it or not, since you've opted out of any compiler verification.
That's a very weak argument since you're already opting out of every other kind of compiler check by using `interface{}` as an element type to begin with.

Yet another reason to just use `interface{}`: if the caller already has a value of type `[]Concrete` then they can just pass it in directly; otherwise the caller will need to allocate and copy the value's elements into a new value of `[]interface{}` type since the `[]Concrete` is not castable: https://play.golang.org/p/whh1mAAlN-N

My point was that it is a bad idea to write functions that take an interface{} and try to reflect on it, since you are putting out of all compiler checks. You also get a much, much slower function.
The other much less painful maxim is to take a pass on Go, and use a language that's not stuck in the early 70s and with a rabid fanbase to boot.
The problem is that there is no way to pass an immutable slice to a function.

What Go really needs is a way to create abstractions that will work with the range loop syntax.

Not sure what you read or why bother commenting when you didn't but this sentence was in the article:

"But slices have mutable indexes. and many functions mutate the slices they're passed."

I read that sentence, but it refers to a different problem. The author is trying to explain why you can't fix the problem by simply having the runtime automatically copy your []struct to a []interface (if it did so, any operation which was supposed to modify the original slice would instead modify the auto-created copy, which wouldn't be correct):

> It could allocate a new slice of interface values for you, and assign the concrete User values into corresponding indexes:

> [snipping example]

> Indeed, for getNames(users) this would actually work fine! But slices have mutable indexes. and many functions mutate the slices they're passed.

Again, this is not a real problem, and it could be pretty easily worked around. The real problem is that the slice type is neither covariant nor contravariant.

>Not sure what you read or why bother commenting when you didn't

Not sure why you felt the need to be rude, when you could omit the above phrase, and just say that the article already covers that (and also be a little humble, in case the grandparent already knew that, but doesn't believe it addresses the core issue).

I'm not sure the implementation details can be ignored so easily. With certain exception, the Go authors do not like implicit conversions and the associated runtime costs.

I look at the discussion of covariance/contravariance and memory layout as two sides of the same coin.

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

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.

(comment deleted)
An article about covariance/contravariance that doesn't once mention either terms...