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);
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow