Zoeken…


Invoering

Kenmerken zijn een manier om een 'contract' te beschrijven dat een struct moet uitvoeren. Kenmerken definiëren meestal handtekeningen van de methode, maar kunnen ook implementaties bieden op basis van andere methoden van de eigenschap, mits de eigenschapsgrenzen dit toestaan.

Voor degenen die bekend zijn met objectgeoriënteerd programmeren, kunnen eigenschappen worden beschouwd als interfaces met enkele subtiele verschillen.

Syntaxis

  • eigenschap eigenschap {fn methode (...) -> ReturnType; ...}
  • eigenschap Trait: Bound {fn methode (...) -> ReturnType; ...}
  • impl Trait voor Type {fn methode (...) -> ReturnType {...} ...}
  • impl <T> eigenschap voor T waarbij T: Bounds {fn methode (...) -> ReturnType {...} ...}

Opmerkingen

  • Kenmerken worden meestal vergeleken met interfaces, maar het is belangrijk om onderscheid te maken tussen de twee. In OO-talen zoals Java zijn interfaces een integraal onderdeel van de klassen die deze uitbreiden. In Rust weet de compiler niets van de eigenschappen van een struct, tenzij die eigenschappen worden gebruikt.

Basics

Een eigenschap maken

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

Een eigenschap implementeren

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

Statische en dynamische verzending

Het is mogelijk om een functie te maken die objecten accepteert die een specifieke eigenschap implementeren.

Statische verzending

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

Statische verzending wordt hier gebruikt, wat betekent dat Rust compiler gespecialiseerde versies van zal genereren generic_speak functie voor zowel Dog en Person types. Deze generatie gespecialiseerde versies van een polymorfe functie (of een polymorfe entiteit) tijdens het compileren wordt monomorfisatie genoemd .

Dynamische verzending

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 bestaat slechts één versie van generic_speak in het gecompileerde binaire bestand en wordt de aanroep speak() gebruikt tijdens het uitvoeren van een vtable- lookup tijdens runtime. Het gebruik van dynamische verzending resulteert dus in een snellere compilatie en een kleiner formaat van gecompileerde binaire bestanden, terwijl deze tijdens de uitvoering iets langzamer zijn.

Objecten van het type &Speak of Box<Speak> worden eigenschapobjecten genoemd.

Bijbehorende typen

  • Gebruik het bijbehorende type wanneer er een één-op-één relatie is tussen het type dat de eigenschap implementeert en het bijbehorende type.
  • Het wordt soms ook het uitvoertype genoemd , omdat dit een item is dat aan een type wordt gegeven wanneer we er een eigenschap op toepassen.

schepping

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

uitvoeringsprogramma

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

Verwijzend naar bijbehorende types

Als we er zeker van zijn dat een type T GetItems implementeert GetItems bijvoorbeeld in het GetItems , kunnen we eenvoudig T::First gebruiken om het bijbehorende type te verkrijgen.

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

Anders moet u de compiler expliciet vertellen welke eigenschap het type implementeert

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

Beperking met bijbehorende 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()
}

Standaard methoden

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

De methode wordt standaard aangeroepen, behalve als deze wordt overschreven in het impl blok.

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

Uitgang:

De mens zegt hallo.

De kat zegt Miauw.

Een grens op een eigenschap plaatsen

Bij het definiëren van een nieuwe eigenschap is het mogelijk om af te dwingen dat types die deze eigenschap willen implementeren een aantal beperkingen of grenzen verifiëren.

Als we een voorbeeld nemen uit de standaardbibliotheek, vereist de DerefMut eigenschap dat een type eerst zijn Deref eigenschap implementeert:

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

Dit stelt op zijn beurt DerefMut in staat om het bijbehorende type Target gedefinieerd door Deref .


Hoewel de syntaxis doet denken aan overerving:

  • het brengt alle bijbehorende items (constanten, types, functies, ...) van de gebonden eigenschap binnen
  • het maakt polymorfisme mogelijk van &DerefMut naar &Deref

Dit is anders van aard:

  • het is mogelijk om een levensduur (zoals 'static ) als een grens te gebruiken
  • het is niet mogelijk om de gebonden kenmerkitems te overschrijven (zelfs niet de functies)

Het is dus het beste om het als een apart concept te beschouwen.

Meerdere gebonden objecttypen

Het is ook mogelijk om meerdere objecttypen toe te voegen aan een statische verzending .

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow