Ich bin relativ vertraut mit Go, da ich eine Reihe kleiner Programme darin geschrieben habe. Rust ist mir natürlich weniger vertraut, aber ich muss ein Auge darauf haben.
Nachdem ich kürzlich http://yager.io/programming/go.html gelesen hatte , dachte ich, ich würde die beiden Möglichkeiten des Umgangs mit Generika persönlich untersuchen, da der Artikel Go zu unfair kritisiert zu haben schien, als es in der Praxis nicht viel gab, was Schnittstellen betraf konnte nicht elegant erreichen. Ich hörte immer wieder den Hype darüber, wie mächtig Rusts Züge waren und nichts als Kritik von Leuten über Go. Nachdem ich einige Erfahrungen in Go gesammelt hatte, fragte ich mich, wie wahr das war und was die Unterschiede letztendlich waren. Was ich fand, war, dass Eigenschaften und Schnittstellen ziemlich ähnlich sind! Letztendlich bin ich mir nicht sicher, ob ich etwas vermisse. Hier ist ein kurzer Überblick über die Ähnlichkeiten, sodass Sie mir sagen können, was ich verpasst habe!
Werfen wir nun einen Blick auf Go Interfaces in der Dokumentation :
Schnittstellen in Go bieten eine Möglichkeit, das Verhalten eines Objekts anzugeben: Wenn dies möglich ist, kann es hier verwendet werden.
Die mit Abstand häufigste Schnittstelle ist Stringer
die, die eine Zeichenfolge zurückgibt, die das Objekt darstellt.
type Stringer interface {
String() string
}
Jedes Objekt, das darauf String()
definiert ist, ist ein Stringer
Objekt. Dies kann in Typensignaturen verwendet werden, bei denen func (s Stringer) print()
fast alle Objekte erfasst und gedruckt werden.
Wir haben auch interface{}
welche Objekte nimmt. Wir müssen dann den Typ zur Laufzeit durch Reflektion bestimmen.
Werfen wir nun einen Blick auf die Rostmerkmale in ihrer Dokumentation :
Im einfachsten Fall ist ein Merkmal eine Menge von null oder mehr Methodensignaturen. Zum Beispiel könnten wir das Merkmal Printable für Dinge deklarieren, die mit einer einzelnen Methodensignatur auf der Konsole gedruckt werden können:
trait Printable {
fn print(&self);
}
Dies sieht unseren Go-Interfaces sofort sehr ähnlich. Der einzige Unterschied, den ich sehe, besteht darin, dass wir 'Implementierungen' von Merkmalen definieren, anstatt nur die Methoden zu definieren. So machen wir es
impl Printable for int {
fn print(&self) { println!("{}", *self) }
}
Anstatt von
fn print(a: int) { ... }
Bonusfrage: Was passiert in Rust, wenn Sie eine Funktion definieren, die ein Merkmal implementiert, aber nicht verwendet impl
? Es funktioniert einfach nicht?
Im Gegensatz zu Go's Interfaces verfügt Rusts Typsystem über Typparameter, mit denen Sie die richtigen Generika und ähnliches ausführen können, interface{}
während der Compiler und die Laufzeit den Typ tatsächlich kennen. Zum Beispiel,
trait Seq<T> {
fn length(&self) -> uint;
}
Funktioniert auf jedem Typ und der Compiler weiß, dass der Typ der Sequence-Elemente zur Kompilierungszeit nicht reflektiert wird.
Nun die eigentliche Frage: Vermisse ich hier irgendwelche Unterschiede? Sind sie wirklich so ähnlich? Gibt es nicht noch einen grundsätzlichen Unterschied, den ich hier vermisse? (Im Gebrauch. Implementierungsdetails sind interessant, aber letztendlich unwichtig, wenn sie gleich funktionieren.)
Neben den syntaktischen Unterschieden sehe ich folgende Unterschiede:
- Go hat einen automatischen Methodenversand. Rust benötigt (?)
impl
, Um ein Merkmal zu implementieren- Elegant gegen explizit
- Rust hat Typparameter, die korrekte Generika ohne Reflektion ermöglichen.
- Go hat hier wirklich keine Antwort. Dies ist das einzige, was erheblich leistungsfähiger ist und letztendlich nur ein Ersatz für das Kopieren und Einfügen von Methoden mit unterschiedlichen Typensignaturen ist.
Sind dies die einzigen nicht trivialen Unterschiede? In diesem Fall scheint das Interface / Type-System von Go in der Praxis nicht so schwach zu sein, wie es angenommen wird.
AnyMap
ist eine gute Demonstration der Stärken von Rust, indem sie Merkmalsobjekte mit Generika kombiniert, um eine sichere und ausdrucksstarke Abstraktion der fragilen Dinge zu liefern, die in Go notgedrungen geschrieben werden müsstenmap[string]interface{}
.