Sök…


Introduktion

Egenskaper är ett sätt att beskriva ett "kontrakt" som en struct måste implementera. Egenskaper definierar vanligtvis metodsignaturer men kan också tillhandahålla implementeringar baserade på andra metoder för egenskaperna, förutsatt att draggränserna tillåter detta.

För dem som är bekanta med objektorienterad programmering kan egenskaper betraktas som gränssnitt med vissa subtila skillnader.

Syntax

  • trait Trait {fn method (...) -> ReturnType; ...}
  • drag Egenskap: bunden {fn metod (...) -> ReturnType; ...}
  • impl Trait for Type {fn method (...) -> ReturnType {...} ...}
  • impl <T> Egenskap för T där T: gränser {fn metod (...) -> ReturnType {...} ...}

Anmärkningar

  • Egenskaper liknar vanligtvis gränssnitt, men det är viktigt att göra en åtskillnad mellan de två. I OO-språk som Java är gränssnitt en integrerad del av klasserna som utökar dem. I Rust vet kompilatorn ingenting om en strukt's drag om inte dessa egenskaper används.

Grunderna

Skapa en egenskap

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

Implementera en egenskap

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

Statisk och dynamisk avsändning

Det är möjligt att skapa en funktion som accepterar objekt som implementerar ett specifikt drag.

Statisk utsändning

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

Statisk utsändning används här, vilket innebär att rostkompileraren kommer att generera specialiserade versioner av generic_speak funktion för både Dog och Person . Denna generation av specialiserade versioner av en polymorfisk funktion (eller någon polymorf enhet) under sammanställningen kallas Monomorphization .

Dynamisk utsändning

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
}

Här finns det bara en enda version av generic_speak i den sammanställda binären, och samtalet speak() görs med hjälp av en vtable- uppslagning vid körning. Således använder dynamisk avsändning snabbare sammanställning och mindre storlek på sammanställd binär, medan den är något långsammare vid körning.

Objekt av typen &Speak eller Box<Speak> kallas dragobjekt .

Tillhörande typer

  • Använd tillhörande typ när det finns en en-mot-en-relation mellan den typ som implementerar egenskaperna och den tillhörande typen.
  • Det är ibland också känt som utgångstyp , eftersom det här är en artikel som ges till en typ när vi tillämpar en egenskap på den.

Skapande

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

implementering

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

Med hänvisning till tillhörande typer

Om vi är säkra på att en typ T implementerar GetItems t.ex. i generik, kan vi helt enkelt använda T::First att få den tillhörande typen.

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

Annars måste du uttryckligen berätta kompilatorn vilken typ av typ som implementerar

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

Begränsa med tillhörande typer

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

Standardmetoder

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

Metoden kommer att kallas som standard förutom om den skrivs över i impl blocket.

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

Utgång:

Människan säger hej.

Katten säger Meow.

Placera en bunden på en egenskap

När du definierar en ny egenskap är det möjligt att säkerställa att typer som vill implementera detta drag verifierar ett antal begränsningar eller gränser.

Med ett exempel från standardbiblioteket kräver DerefMut drag att en typ först implementerar sitt syskon Deref drag:

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

Detta möjliggör i sin tur att DerefMut kan använda den tillhörande typen Target definierad av Deref .


Medan syntaxen kan påminna om arv:

  • det tar in alla tillhörande objekt (konstanter, typer, funktioner, ...) av den bundna egenskapen
  • det möjliggör polymorfism från &DerefMut till &Deref

Detta har olika karaktär:

  • det är möjligt att använda en livstid (som 'static ) som en bunden
  • det är inte möjligt att åsidosätta de bundna egenskaperna (inte ens funktionerna)

Därför är det bäst att tänka på det som ett separat koncept.

Flera bundna objekttyper

Det är också möjligt att lägga till flera objekttyper till en statisk avsändningsfunktion .

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow