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