I am relatively familiar with Go, having written a number of small programs in it. Rust, of course, I am less familiar with but keeping an eye on.
Having recently read http://yager.io/programming/go.html, I thought I'd personally examine the two ways Generics are handled because the article seemed to unfairly criticize Go when, in practice, there wasn't much that Interfaces couldn't accomplish elegantly. I kept hearing the hype about how powerful Rust's Traits were and nothing but criticism from people about Go. Having some experience in Go, I wondered how true it was and what the differences ultimately were. What I found was that Traits and Interfaces are pretty similar! Ultimately, I'm not sure if I'm missing something, so here is a quick educational rundown of their similarities so you can tell me what I missed!
Now, let's take a look at Go Interfaces from their documentation:
Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here.
By far the most common interface is Stringer
which returns a string representing the object.
type Stringer interface {
String() string
}
So, any object that has String()
defined on it is a Stringer
object. This can be used in type signatures such that func (s Stringer) print()
takes almost all objects and prints them.
We also have interface{}
which takes any object. We must then determine the type at runtime through reflection.
Now, let's take a look at Rust Traits from their documentation:
At its simplest, a trait is a set of zero or more method signatures. For example, we could declare the trait Printable for things that can be printed to the console, with a single method signature:
trait Printable {
fn print(&self);
}
This immediately looks quite similar to our Go Interfaces. The only difference I see is that we define 'Implementations' of Traits rather than just defining the methods. So, we do
impl Printable for int {
fn print(&self) { println!("{}", *self) }
}
instead of
fn print(a: int) { ... }
Bonus Question: What happens in Rust if you define a function that implements a trait but you don't use impl
? It just doesn't work?
Unlike Go's Interfaces, Rust's type system has type parameters which let you do proper generics and things like interface{}
while the compiler and the runtime actually know the type. For example,
trait Seq<T> {
fn length(&self) -> uint;
}
works on any type and the compiler knows that the type of the Sequence elements at compile time rather than using reflection.
Now, the actual question: am I missing any differences here? Are they really that similar? Is there not some more fundamental difference that I'm missing here? (In usage. Implementation details are interesting, but ultimately not important if they function the same.)
Besides syntactic differences, the actual differences I see are:
- Go has automatic method dispatch vs. Rust requires(?)
impl
s to implement a Trait- Elegant vs. Explicit
- Rust has type parameters which allow for proper generics without reflection.
- Go really has no response here. This is the only thing that is significantly more powerful and it's ultimately just a replacement for copying and pasting methods with different type signatures.
Are these the only non-trivial differences? If so, it would appear Go's Interface/Type system is, in practice, not as weak as perceived.