Szukaj…


Wprowadzenie

Cechy są sposobem na opisanie „umowy”, którą musi wdrożyć struct . Cechy zwykle definiują podpisy metod, ale mogą także zapewniać implementacje oparte na innych metodach cechy, pod warunkiem, że pozwalają na to granice cechy .

Dla osób zaznajomionych z programowaniem obiektowym cechy można traktować jako interfejsy z pewnymi subtelnymi różnicami.

Składnia

  • trait Trait {metoda fn (...) -> ReturnType; ...}
  • trait Trait: Bound {fn method (...) -> ReturnType; ...}
  • impl Trait dla metody Type {fn (...) -> ReturnType {...} ...}
  • impl <T> Cecha dla T gdzie T: Bounds {metoda fn (...) -> ReturnType {...} ...}

Uwagi

  • Cechy są zwykle porównywane do interfejsów, ale ważne jest, aby wprowadzić rozróżnienie między nimi. W językach OO, takich jak Java, interfejsy są integralną częścią klas, które je rozszerzają. W Rust kompilator nie wie nic o cechach struktury, chyba że te cechy są użyte.

Podstawy

Tworzenie cechy

trait Speak {
    fn speak(&self) -> String;
}

Wdrażanie cechy

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

Wysyłka statyczna i dynamiczna

Możliwe jest utworzenie funkcji, która akceptuje obiekty, które implementują określoną cechę.

Wysyłka statyczna

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

Stosowana jest tutaj statyczna wysyłka, co oznacza, że kompilator Rust będzie generował specjalistyczne wersje funkcji generic_speak zarówno Dog typów Dog jak i Person . Ta generacja specjalistycznych wersji funkcji polimorficznej (lub dowolnej jednostki polimorficznej) podczas kompilacji nazywa się monomorfizacją .

Dynamiczna wysyłka

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
}

Tutaj w skompilowanym pliku binarnym istnieje tylko jedna wersja generic_speak , a wywołanie speak() jest wykonywane przy użyciu sprawdzania vtable w środowisku wykonawczym. Zatem użycie dynamicznej wysyłki powoduje szybszą kompilację i mniejszy rozmiar skompilowanego pliku binarnego, a jednocześnie jest nieco wolniejszy w czasie wykonywania.

Obiekty typu &Speak lub Box<Speak> nazywane są obiektami cech .

Powiązane typy

  • Użyj powiązanego typu, gdy istnieje relacja jeden do jednego między typem implementującym cechę a powiązanym typem.
  • Czasami jest również znany jako typ wyjściowy , ponieważ jest to element nadawany typowi, gdy zastosujemy do niego jakąś cechę.

kreacja

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.
}

Realizacja

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

Odnosząc się do powiązanych typów

Jeśli jesteśmy pewni, że typ T implementuje GetItems np. W GetItems , możemy po prostu użyć T::First aby uzyskać skojarzony typ.

fn get_first_and_last<T: GetItems>(obj: &T) -> (&T::First, &T::Last) {
//                                               ^~~~~~~~ refer to an associated type
    (obj.first_item(), obj.last_item())
}

W przeciwnym razie musisz wyraźnie powiedzieć kompilatorowi, jaką cechę implementuje ten typ

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

Ograniczanie z powiązanymi typami

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

Metody domyślne

trait Speak {
    fn speak(&self) -> String {
        String::from("Hi.")
    }
}

Metoda będzie wywoływana domyślnie, chyba że zostanie zastąpiona w bloku impl .

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

Wynik :

Człowiek mówi Cześć.

Kot mówi Meow.

Umieszczenie granicy cechy

Podczas definiowania nowej cechy możliwe jest wymuszenie tego typu, który chce wdrożyć tę cechę, zweryfikować szereg ograniczeń lub ograniczeń.

Biorąc przykład ze standardowej biblioteki, cecha DerefMut wymaga, aby typ najpierw zaimplementował swoją cechę rodzeństwa Deref :

pub trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

To z kolei umożliwia DerefMut korzystanie z powiązanego typu Target zdefiniowanego przez Deref .


Chociaż składnia może przypominać dziedziczenie:

  • wprowadza wszystkie powiązane elementy (stałe, typy, funkcje, ...) związanej cechy
  • umożliwia polimorfizm z &DerefMut do &Deref

Ma to inny charakter:

  • możliwe jest użycie czasu życia (takiego jak 'static ) jako ograniczenia
  • nie można przesłonić powiązanych elementów cechy (nawet funkcji)

Dlatego najlepiej myśleć o tym jako o osobnej koncepcji.

Wiele powiązanych typów obiektów

Możliwe jest również dodanie wielu typów obiektów do funkcji wysyłki statycznej .

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow