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