Suche…
Einführung
Merkmale sind ein Weg, um einen "Vertrag" zu beschreiben, den eine struct
implementieren muss. Merkmale definieren typischerweise Methodensignaturen, können jedoch auch Implementierungen bereitstellen, die auf anderen Methoden des Merkmals basieren, sofern die Merkmalsgrenzen dies zulassen.
Für Personen, die mit der objektorientierten Programmierung vertraut sind, können Merkmale als Schnittstellen mit geringfügigen Unterschieden betrachtet werden.
Syntax
- Eigenschaft Eigenschaft {Fn-Methode (...) -> ReturnType; ...}
- Merkmal Merkmal: Gebundene {fn-Methode (...) -> ReturnType; ...}
- impl Eigenschaft für Typ {fn-Methode (...) -> ReturnType {...} ...}
- impl <T> Eigenschaft für T wobei T: Bounds {fn-Methode (...) -> ReturnType {...} ...}
Bemerkungen
- Merkmale werden im Allgemeinen mit Schnittstellen verglichen, aber es ist wichtig, zwischen den beiden zu unterscheiden. In OO-Sprachen wie Java sind Schnittstellen ein wesentlicher Bestandteil der Klassen, die sie erweitern. In Rust kennt der Compiler nichts von den Eigenschaften einer Struktur, es sei denn, diese Eigenschaften werden verwendet.
Grundlagen
Eine Eigenschaft erstellen
trait Speak {
fn speak(&self) -> String;
}
Ein Merkmal implementieren
struct Person;
struct Dog;
impl Speak for Person {
fn speak(&self) -> String {
String::from("Hello.")
}
}
impl Speak for Dog {
fn speak(&self) -> String {
String::from("Woof.")
}
}
fn main() {
let person = Person {};
let dog = Dog {};
println!("The person says {}", person.speak());
println!("The dog says {}", dog.speak());
}
Statischer und dynamischer Versand
Es ist möglich, eine Funktion zu erstellen, die Objekte akzeptiert, die eine bestimmte Eigenschaft implementieren.
Statischer Versand
fn generic_speak<T: Speak>(speaker: &T) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person);
generic_speak(&dog);
}
Statischer Versand wird hier verwendet, was bedeutet, dass der Rust-Compiler spezialisierte Versionen der generic_speak
Funktion für Dog
und Person
generiert. Diese Generation von spezialisierten Versionen einer polymorphen Funktion (oder einer beliebigen polymorphen Entität) während der Kompilierung wird Monomorphisierung genannt .
Dynamischer Versand
fn generic_speak(speaker: &Speak) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person as &Speak);
generic_speak(&dog); // gets automatically coerced to &Speak
}
Hier ist nur eine einzige Version von generic_speak
in der kompilierten Binärdatei vorhanden, und der Aufruf von speak()
erfolgt zur Laufzeit über eine vtable- Suche. Daher führt die Verwendung von dynamischem Dispatch zu einer schnelleren Kompilierung und einer kleineren Größe der kompilierten Binärdatei, während sie zur Laufzeit etwas langsamer ist.
Objekte vom Typ &Speak
oder Box<Speak>
werden Merkmalsobjekte genannt .
Zugehörige Typen
- Verwenden Sie einen verknüpften Typ, wenn eine Eins-zu-Eins-Beziehung zwischen dem Typ besteht, der die Eigenschaft implementiert, und dem zugeordneten Typ.
- Es wird manchmal auch als Ausgabetyp bezeichnet , da dies ein Element ist, das einem Typ gegeben wird, wenn wir ein Merkmal darauf anwenden.
Schaffung
trait GetItems {
type First;
// ^~~~ defines an associated type.
type Last: ?Sized;
// ^~~~~~~~ associated types may be constrained by traits as well
fn first_item(&self) -> &Self::First;
// ^~~~~~~~~~~ use `Self::` to refer to the associated type
fn last_item(&self) -> &Self::Last;
// ^~~~~~~~~~ associated types can be used as function output...
fn set_first_item(&mut self, item: Self::First);
// ^~~~~~~~~~~ ... input, and anywhere.
}
Umsetzung
impl<T, U: ?Sized> GetItems for (T, U) {
type First = T;
type Last = U;
// ^~~ assign the associated types
fn first_item(&self) -> &Self::First { &self.0 }
fn last_item(&self) -> &Self::Last { &self.1 }
fn set_first_item(&mut self, item: Self::First) { self.0 = item; }
}
impl<T> GetItems for [T; 3] {
type First = T;
type Last = T;
fn first_item(&self) -> &T { &self[0] }
// ^ you could refer to the actual type instead of `Self::First`
fn last_item(&self) -> &T { &self[2] }
fn set_first_item(&mut self, item: T) { self[0] = item; }
}
Zugehörige Typen referenzieren
Wenn wir sicher sind, dass ein Typ T
GetItems
beispielsweise in Generics implementiert, könnten wir einfach T::First
, um den zugehörigen Typ zu erhalten.
fn get_first_and_last<T: GetItems>(obj: &T) -> (&T::First, &T::Last) {
// ^~~~~~~~ refer to an associated type
(obj.first_item(), obj.last_item())
}
Andernfalls müssen Sie dem Compiler explizit mitteilen, welche Eigenschaft der Typ implementiert
let array: [u32; 3] = [1, 2, 3];
let first: &<[u32; 3] as GetItems>::First = array.first_item();
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [u32; 3] may implement multiple traits which many
// of them provide the `First` associated type.
// thus the explicit "cast" is necessary here.
assert_eq!(*first, 1);
Einschränkung mit zugehörigen Typen
fn clone_first_and_last<T: GetItems>(obj: &T) -> (T::First, T::Last)
where T::First: Clone, T::Last: Clone
// ^~~~~ use the `where` clause to constraint associated types by traits
{
(obj.first_item().clone(), obj.last_item().clone())
}
fn get_first_u32<T: GetItems<First=u32>>(obj: &T) -> u32 {
// ^~~~~~~~~~~ constraint associated types by equality
*obj.first_item()
}
Standardmethoden
trait Speak {
fn speak(&self) -> String {
String::from("Hi.")
}
}
Die Methode wird standardmäßig aufgerufen, sofern sie nicht im impl
Block überschrieben wird.
struct Human;
struct Cat;
impl Speak for Human {}
impl Speak for Cat {
fn speak(&self) -> String {
String::from("Meow.")
}
}
fn main() {
let human = Human {};
let cat = Cat {};
println!("The human says {}", human.speak());
println!("The cat says {}", cat.speak());
}
Ausgabe :
Der Mensch sagt Hallo.
Die Katze sagt Miau.
Eine Eigenschaft an eine Grenze setzen
Bei der Definition einer neuen Eigenschaft können Sie festlegen, dass die Typen, die diese Eigenschaft implementieren möchten, eine Reihe von Einschränkungen oder Grenzen überprüfen.
Ein Beispiel aus der Standardbibliothek erfordert für die DerefMut
Eigenschaft, dass ein Typ zuerst seine Deref
Eigenschaft implementiert:
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Dies wiederum ermöglicht es DerefMut
, den von Deref
definierten zugehörigen Typ Target
Deref
.
Während die Syntax an Vererbung erinnert:
- es bringt alle zugehörigen Elemente (Konstanten, Typen, Funktionen, ...) des gebundenen Merkmals mit ein
- Es ermöglicht Polymorphismus von
&DerefMut
bis&Deref
Das ist in der Natur anders:
- Es ist möglich, eine Lebensdauer (z. B.
'static
) als Grenze zu verwenden - Es ist nicht möglich, die gebundenen Merkmalselemente (auch nicht die Funktionen) zu überschreiben.
Daher ist es am besten, es als separates Konzept zu betrachten.
Mehrere gebundene Objekttypen
Es ist auch möglich, einer Static Dispatch- Funktion mehrere Objekttypen hinzuzufügen.
fn mammal_speak<T: Person + Dog>(mammal: &T) {
println!("{0}", mammal.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
mammal_speak(&person);
mammal_speak(&dog);
}