Go Interfaces, the Tricky Parts
timr.co
timr.co
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.
[1] https://eli.thegreenplace.net/2018/covariance-and-contravari...
https://en.wikipedia.org/wiki/Covariance_and_contravariance_...
Going after two folks in Java and C# communities (the later being involved in developing the language) for committing the same error.
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.
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 :-).
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 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.
"When writing generic Go, only accept `interface{}` and never a slice" is a painful maxim but works.
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
What Go really needs is a way to create abstractions that will work with the range loop syntax.
I look at the discussion of covariance/contravariance and memory layout as two sides of the same coin.
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.
var _ MyInterface = (*MyImplementation)(nil)
https://github.com/golang/go/wiki/CodeReviewComments#interfa...
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).
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.
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.
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.
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.
In a statically compiled binary, the compiler will complain at every conversion if the interface is not satisfied.
Generic interfaces (e.g. Reader) could be used in 20000 different places, packages, repos, etc...
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>.
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.
Line 2: `User` is undefined... can't read the rest of the article.