Dies wird jetzt in der zweiten Ausgabe von The Rust Programming Language angesprochen . Lassen Sie uns jedoch zusätzlich ein wenig eintauchen.
Beginnen wir mit einem einfacheren Beispiel.
Wann ist es angebracht, eine Merkmalsmethode anzuwenden?
Es gibt mehrere Möglichkeiten, eine späte Bindung bereitzustellen :
trait MyTrait {
fn hello_word(&self) -> String;
}
Oder:
struct MyTrait<T> {
t: T,
hello_world: fn(&T) -> String,
}
impl<T> MyTrait<T> {
fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>;
fn hello_world(&self) -> String {
(self.hello_world)(self.t)
}
}
In beiden obigen Auszügen kann der Benutzer ohne Berücksichtigung einer Implementierungs- / Leistungsstrategie auf dynamische Weise angeben, wie hello_world
sich verhalten soll.
Der einzige Unterschied (semantisch) besteht darin, dass die trait
Implementierung garantiert, dass für einen bestimmten Typ, der das T
implementiert trait
, hello_world
das immer das gleiche Verhalten aufweist, während dasstruct
Implementierung ein unterschiedliches Verhalten pro Instanz zulässt.
Ob die Verwendung einer Methode angemessen ist oder nicht, hängt vom Anwendungsfall ab!
Wann ist es angebracht, einen zugeordneten Typ zu verwenden?
Ähnlich wie bei den trait
obigen Methoden ist ein zugeordneter Typ eine Form der späten Bindung (obwohl sie bei der Kompilierung auftritt), sodass der Benutzer von trait
für eine bestimmte Instanz angeben kann, welcher Typ ersetzt werden soll. Es ist nicht der einzige Weg (also die Frage):
trait MyTrait {
type Return;
fn hello_world(&self) -> Self::Return;
}
Oder:
trait MyTrait<Return> {
fn hello_world(&Self) -> Return;
}
Entsprechen der späten Bindung der oben genannten Methoden:
- Der erste erzwingt, dass für eine bestimmte
Self
Person eine einzelne Return
zugeordnet ist
- die zweite, sondern ermöglicht die Umsetzung
MyTrait
für Self
für mehrereReturn
Welche Form besser geeignet ist, hängt davon ab, ob es sinnvoll ist, die Einheit durchzusetzen oder nicht. Beispielsweise:
Deref
verwendet einen zugeordneten Typ, da der Compiler ohne Eindeutigkeit während der Inferenz verrückt werden würde
Add
verwendet einen zugeordneten Typ, da der Autor der Ansicht war, dass es angesichts der beiden Argumente einen logischen Rückgabetyp geben würde
Wie Sie sehen können, Deref
ist der Fall von zwar ein offensichtlicher Anwendungsfall (technische Einschränkung), Add
aber weniger eindeutig: Vielleicht wäre es sinnvoll i32 + i32
, entweder i32
oder Complex<i32>
abhängig vom Kontext nachzugeben ? Der Autor übte jedoch sein Urteil aus und entschied, dass eine Überladung des Rückgabetyps für Ergänzungen nicht erforderlich sei.
Meine persönliche Haltung ist, dass es keine richtige Antwort gibt. Über das Unicity-Argument hinaus möchte ich jedoch erwähnen, dass zugeordnete Typen die Verwendung des Merkmals erleichtern, da sie die Anzahl der Parameter verringern, die angegeben werden müssen. Falls die Vorteile der Flexibilität der Verwendung eines regulären Merkmalsparameters nicht offensichtlich sind, I. Schlagen Sie vor, mit einem zugeordneten Typ zu beginnen.
trait/struct MyTrait/MyStruct
erlaubt genau eineimpl MyTrait for
oderimpl MyStruct
.trait MyTrait<Return>
erlaubt mehrereimpl
s, weil es generisch ist.Return
kann jeder Typ sein. Generische Strukturen sind gleich.Zugehörige Typen sind ein Gruppierungsmechanismus. Sie sollten daher verwendet werden, wenn es sinnvoll ist, Typen zu gruppieren.
Das
Graph
in der Dokumentation eingeführte Merkmal ist ein Beispiel dafür. Sie möchtenGraph
, dass a generisch ist, aber sobald Sie eine bestimmte Art von habenGraph
, möchten Sie nicht, dass dieNode
oderEdge
Typen mehr variieren. Ein bestimmterGraph
Benutzer möchte diese Typen nicht innerhalb einer einzelnen Implementierung variieren und möchte, dass sie immer gleich sind. Sie sind zusammen gruppiert, oder man könnte sogar sagen assoziiert .quelle