Interfaces are great
They're honestly brilliant. Interfaces allow for simple, elegant programming. Let's use Java to get a quick idea of how to use interfaces.
package mypackage;
public interface Edible {
public void eat();
}
Now, a Burger
class to implement Edible
.
package mypackage;
public class Burger implements Edible {
public void eat() {
System.out.println("Burgers, yum!");
}
}
This means that we can use our Burger
class wherever we see Edible
, such as in method arguments!
The problem with interfaces
But let's say that we are using a library, and that library has a class called Salad
. Here's what Salad
looks like:
package theirpackage;
public class Salad {
public void eat() {
System.out.println("I'm being healthy!")
}
}
So Salad
has an eat()
method, but because the other library doesn't even know about Edible
interface exists, it obviously can't implement it. This problem has definitely been encountered before, so let's talk about how Go has fixed it.
How Go fixes interfaces
With how Go interfaces work, you don't need to declare an interface implementation. If you implement the proper methods, you implement the interface. This is very prominent in Go, and is a key feature. Let's use our previous example.
First, let's remake our Edible
interface.
package mypackage
type Edible interface {
Eat()
}
Now, let's make a Burger
struct that implements Edible
.
package mypackage
type Burger struct {}
// How to define a method in Go
func (b Burger) Eat() {
fmt.Println("Burgers, yum!")
}
Notice that we never acknowledged Edible
anywhere while we made the Burger
struct. This is because any type that has an Eat()
method associated with it is considered Edible
. This means that even types from other libraries can be Edible
, as long as they have an Eat()
method!
This means that even this Salad
struct from a different package is Edible
.
package theirpackage
type Salad struct {}
func (s Salad) Eat() {
fmt.Println("I'm being healthy!")
}
This shows just how awesome Golang is. I highly recommend watching their video, A Tour of Go, which shows all of the brilliant features that Go has to offer.
EDIT: I really like the discussion on this post! One of the key things that I didn't mention here is that interfaces in Go should be used to describe simple patterns. Here is a clip from A Tour of Go that should hopefully describe how interfaces in Go should be used compared to how they're used in OOP.
Top comments (46)
What about name collisions? This seems so wrong to me, how is type-safety enforced?
The Go developers believe that, with interfaces, you mostly care about what something can do, not what something is. So, instead of having a rigid type system with inheritance and all that mess, they allow functions and methods to just declare "I need something that can do these things." If you're coming from a more complex type system, like Java (which is where I came from), it can take some getting used to, but it's really one of my favorite parts of Go. It lets me concentrate on what things need to be doing, not what crazy class tree they're part of.
I also come from Java world. I can see what you mean, but still what is the problem of just saying that this class implements this interface? This is completely unrelated to inheritance.
Because declaring what an object is requires that you know that something exists.
Take that salad example from earlier: even though salads can be eaten, because it doesn't know the Edible interface exists, it isn't "Edible"
In Go, because Salad has an "Eat()" method, it can do everything that an Edible can do, so it is considered Edible.
Well, I stopped to think about this a little more and I don't think I ever used 2 interfaces with the same method names in any of my code ever (although I have not been coding type-safe languages for a while now). I can see why go went this route, but it is hard to measure how many problems this might cause without making a large-scale project.
I still like the idea of specifying in the class what interfaces it implements, for example if a class implements Serializable I know ALL methods of Serializable are implemented there. Instead of having to check all the methods in a class, I can just check the class stub. This very useful when browsing the javadocs, I can just check the class implemented interfaces and see if it implements Serializable and just throw it at a JSON encoder or some other IO operation.
It doesn't cause problems from my experience. Also interfaces in Go still require a full method signature. But in Go, interfaces shouldn't be used to define what a type is, but rather what it does. To tell what a type is, you can use GoDoc.
I re-read your question and realized that no one really answered it. I didn't want to get into the whole concept of packages in Go in the blog post so I didn't go into it. Basically, when referring to a type, variable, or anything, you refer to it through
packagename.whatever
. OurEdible
interface could be referred to asmypackage.Edible
, theBurger
struct could be referred to asmypackage.Burger
, although we can just refer to them asEdible
andBurger
because they are in our package.Meanwhile, the
Salad
struct must be referred to astheirpackage.Salad
because it is in a different package.Also, in terms of type collisions with method names, all arguments and the return type must match the interface's in order for it to be considered "implemented".
A type that implements an interface does so solely by using a value of that type in a position for that interfaces. So a use is a kind of implicit definition.
If a type is used in the position of two interfaces with some or all method signatures in common this should be 1. suspect or 2. based on informal contracts that those common methods are intended to conform to each other. Type safety will be enforced. Intent should be suspect.
Interesting, so there is some type inference going on when you throw an object at a method that expects an Interface as an argument. Thanks for the explanation!
In the case of passing a struct into a function that takes an interface, the compiler enforces interface requirements. It will even tell you what methods are missing from the type which cause it not to fulfill the interface.
This is Go's core value: It's dead simple. Glad you write this. People need to learn about simpler OOP approaches even if they don't plan to implement them. Knowing about how other languages do things ultimately helps you become better.
What happens if the interface changes? How would you even know that happen until runtime? If the interface is an internal one to your application no problem, just add tests to cover that code. But if you are dealing with a thrid party library and trying to conform to an internal interface, this could become a real pain point.
I think you would catch such changes at compile time?
Also, what about dependency management? I think managing dependencies is one of the main reasons for why interfaces exist in the first place (dependency inversion principle). Without explicit declaration of interfaces, how do you know upon which modules or libraries you depend? Do you have an easy way of figuring out on which interface(s) you depend, when you look at one struct/class/whatever-it-is in Go?
Interfaces should not be used to handle dependencies in Go. Go has features specifically for managing dependencies in the CLI with
go get
and such. Go is built around open source, so it's able to use things like git to manage dependencies (import paths for third party libraries are actually the URL to get to their library from). There are also third party managers for people who don't like the way thatgo get
works, by putting dependencies in avendor
folder.There is a bit of responsibility on the library maker and user though. A library maker shouldn't change an interface without documenting it, just as the user shouldn't update a dependency without reading the changes. A change in an interface will still most likely result in compile-time errors, as Go does use a strong typing system.
Good post. Thanks.
If the interface changes, you no longer implement the interface if your struct does not have the matching methods. This will most likely cause issues at compile-time, so you are able to catch it very early.
Wow! Implicitly making a type conform an interface sounds like a really bad idea to me. I mean, what could possibly go wrong? Right?
I like the Swift approach way more where you can explicitly declare that an existing type conforms to an interface (called protocol in Swift) like this:
class Salad: Edible {}
No accidental conformance.
Or as the joke goes: if it walks like a duck and quacks like a duck it probably throws exceptions at runtime.
@Ruotger Deecke: Well said. The author of the article doesn't seem to realize the trouble Go programmers are gonna get in with this.
Let me make an example.
If I have an "Audio" object (parsed and instanced from a random file in a folder) and a "AnimatedGif" object (from the same folder), and both have a "Play" method, I can easily happen to pass the "Audio" object to the "AnimatedGifVisualizer" by error, because both classes have a "Play()" method.
This will work fine until the "Play()" method would be actually called, suddenly crashing everything. And you will NEVER found why. Replace this for any similar scenario using any other data types, locally or server side, and you get the same silent, untraceable crash. This implicit interface conformance thing is a debugging hell.
This is why Go will never compete with better designed langages like Swift.
Because while who designed Swift really thought about this when created Swift protocols, Go designers just throwed in features because those sounded nice at first sight, with no careful assessment of consequences.
If an error happens on your part, it isn't Go's fault for being designed badly, it's most likely yours. In order to crash the system in your example, you need to put your
Audio
struct in a place calledAnimatedGifVisualizer
...In Go, methods have very clear meaning about what they do. If you see a method that's called
CloseThenExit(io.Closer)
in an io library, would you really pass in yourDoor
struct which has aClose()
method?Sounds like a really bad idea to me. What if a class has a method with the same name as an interface method, but with completely different semantics? If you want to use Salad as an Edible, just make an EdibleSaladAdapter, and you're done... Sure, that's more work; but at least it's safe.
If you see that a method takes a Closer ("Closable" in other languages), sure a struct for a door might have a Close() method, but is it really a good idea to pass it to a method like io.CloseAndQuit(Closer)?
In Go, it's usually quite clear what interfaces are intended to be used for, so while it's possible to pass something like a door or a cabinet into an io function, it's pretty clear that you'll get unintended consequences. It might not be as safe in a few select contexts, but you get the huge benefit of convenience that I've described above.
Haskell's approach is interesting as well, and not dissimilar from Go. In Haskell you can declare an interface (but really a typeclass), and then explicitly make a type "part" of that interface. It would be like declaring a class in Java, then declare interface, then you can define an implementation of the interface's method separate from the original class.
This has the benefit of not having to know which interfaces a type follows when you declare the type, as well as having to explicitly make a type to be a part of the interface.
This is really cool. Would be awesome to see in more languages to be honest
Someone sent me a language on twitter that has a similar concept. They had both "traits" and "interfaces", where traits had to be explicitly implemented and interfaces would be more like Go.
That sounds like Scala. It has a type safe concept of structural typing (a lot safer than Go)
While structural typing is safer, the post pretty clearly explains the problems with it. The safety brought by structural typing isn't worth the benefits that it restricts.
That's a pretty broad statement. The best you can argue is a tradeoff for the language features that are right for the product/team/roadmap. I've work for years with static and dynamic languages. They both have strengths and weaknesses. That Go is duck typed is just not a selling point. Many languages do it. I would avoid comparing Go with statically typed, OOP, languages to be honest (unless you fancy a flame war). Go doesn't solve interface problems, it just has a different idea of what an interface is, and it's not that novel. Go should be considered when runtime performance is critical and a simple and effective concurrency paradigm is needed. Go is many things, but it will always have safety issues (maybe that is why the error handling seems to be, err, well).
Go is statically typed. If you were to pass a
Dog
struct (which can'tQuack()
) into a function that takes aDuck
interface, you will hit a compile-time error.If I understand correctly Go isn't even strictly duck typed.
The type inference doesn't happen at runtime but at compile time, giving very clear compiler errors.
So I don't really see how this kind of typing can lead to runtime errors in Go's case
The only time it would result in a runtime error is when casting, as per every staticly typed language.
That's what I thought! So I really don't get all this duck typing trash talk some people are throwing around in regards to Go
Structural subtyping is a terrible approach. It means Go Interfaces can only abstract over a type's structure, but not its laws/semantics/algebra.
For example, not every type with a
combine
function is a monoid (combine
must be associative). In Go, every type withcombine
is automatically a monoid.It only conforms to the interface if it has the correct parameters as well. The examples listed don’t include type information, which was my oversight when trying to make it simple.
Also, interfaces in Go describe what a structure does rather than what it is, and they should be used as such. So something with a
combine
method is usually aCombiner
rather than something with a fancier name.Yes, that was precisely my point. They let you abstract over the signature of a function, but not over its laws. Therefore, Go's expressiveness is limited.
The word awesome is over used. Your main selling point is a developer doesnt need an in depth knowledge of the code base there working on. If developers are forced to work with an interface they will simply implement it. I would not want implementation of my interfaces spread throughout my code base. I think its a code smell. I want to know what my code implements.
Really the main point I was trying to make is that code from other libraries can be easily used in code you make. If you bring in a library which supplies an
HttpRequest
, and you have an interface calledRequester
which has aRequest()
method, as long asHttpRequest
has theRequest()
method, it conforms to theRequester
interface and can be used where you'd want it to be used.Interesting approach. Java people (myself included) would have a hard time with this one. It sounds like it would work well for standard/widely used operations (i.e. GET, POST, DELETE), but also the name collision could be an issue.
HOWEVER, to avoid name collision, one would just have to "know something exists" (so that you dont reuse a function name) in the same way you would in typing systems similar to java. So to me, it could be a worthwhile tradeoff.
Not sure if the Go designers are just trolling or what... You're suggesting that the idea is that a class can implement an interface purely out of coincidence, and that's somehow useful in reality. But, if the interface was implemented accidentally, that means that the author of the implementing class didn't know what behavior the interface specifies for those methods (he/she haven't seen the API docs). So that's exactly the unfortunate case where you don't want to utilize implicit interfaces. If, however, the author of the class have implemented the interface on purpose, it's a big loss that this intent can't be expressed in the language (doing that in comments is obviously inferior; they aren't enforced or checked), as now the code is less self-documenting.
OK, there's a thin chance that both the method signatures and the specified behavior matches, purely out of blind luck, and not because the author deliberately implemented the interface. Given the certainly low chance of that happening, is it worth it to give up said self documenting capability? I mean that's quite a big sacrifice.
First of all - Go isn't object oriented. It doesn't have classes, it has structs. It doesn't have methods, it has functions that operate on structs. OOP interfaces describe what a class is, but Go interfaces describe how a struct behaves.
It's okay to accidentally implement an interface, there's no sacrifice. Sure, you might make a door struct that might have a
close()
function, but in order to mess something up, you'd need to pass it into a method that clearly says it takes anio.Closer
as an argument.Also, these exceptions are still caught at compile time. If you pass a struct that doesnt match a specific interface to a function that requires that interface, it will throw a compile-time error that specifies which functions are missing from the struct.
My point didn't go though. I wasn't talking about uncaught mistakes. My concern is about how self documenting the code will be, and for what was that sacrificed. Let me try again. If a type
T
has a set of functions associated that satisfy theI
interface, and also they behave as described in the documentation ofI
, then most certainly the author ofT
(or of the functions) has deliberately "implemented" theI
interface there. Right? I mean, just how often does such thing happen by chance? If almost never, then why do the Go designers want us to not state that intent (I'm implementingI
) in the language? What's the actual, practical, every day use case for "implementing without stating it"? Surely it's not that Go developers often get extremely lucky and so can avoid adapters.