Cechy (traits)
Definicja i implementacja cechy
-
W pliku z poprzedniego ćwiczenia
rectangle.rs
, zdefiniuj nową cechę (ang. trait) określającą wspólne (sic!) cechy dla wszystkich dwuwymiarowych figur geometrycznych (traitShape
). Na początku pliku dodaj następujący fragment kodu:trait Shape { fn area(&self) -> f32; fn perimeter(&self) -> f32; }
-
Następnie dodaj zdefiniowaną cechę do strutktury
Rectangle
. W tym celu musisz utworzyć nowy blokimpl
, którym dodasz własne implementacje zdefiniowanych metod:impl Shape for Rectangle { fn area(&self) -> f32 { self.x * self.y } fn perimeter(&self) -> f32 { 2f32 * (self.x + self.y) } }
-
Dodaj do funkcji
main
kod, który wywoła nowe metody na utworzynych instancja prostokątów. Sprawdź czy wszystko działa poprawnie.💡Zauważyłaś/eś, że struktura
Rectangle
ma teraz dwie metodyarea
? Która metoda została wywołana w metodziemain
?Cechy mogą rozszerzać funkcje naszych struktur, mogą być również dodawane do istniejących struktur. Istnieje tylko jedna zasada: możemy zaimplementować cechę na typie tylko wtedy, gdy przynajmniej jedna z cech lub typ jest lokalny, tzn. zdefiniowany w aktualnej skrzynce (ang. crate). Na przykład, możemy zaimplementować standardowe cechy biblioteczne, takie jak
Display
, na niestandardowym typie, takim jakRectangle
, jako część funkcjonalności naszej bibliteki, ponieważ typRectangle
jest lokalny. Możemy również zaimplementowaćShape
naVec<T>
w naszej skrzynce, ponieważ cechaShape
jest lokalna.Nie możemy jednak implementować zewnętrznych cech na zewnętrznych typach. Na przykład, nie możemy zaimplementować cechy
Display
naVec<T>
w naszej skrzynce, ponieważDisplay
iVec<T>
są zdefiniowane w bibliotece standardowej i nie są lokalne dla naszej skrzynki. Reguła ta zapewnia, że kod innych osób nie może zepsuć twojego kodu i odwrotnie. Bez tej reguły dwie skrzynki mogłyby zaimplementować tę samą cechę dla tego samego typu, a Rust nie wiedziałby, której implementacji użyć.
Domyślna implementacja metody
Metody definiowane w cechach mogą mieć domyślną implementację lub implementacja ta może być wspólna dla wszystkich typów implementujących daną cechę.
- Dodaj do cechy
shape
metodędescribe
:trait Shape { ... fn describe(&self) { println!("I'm a general shape."); } }
- W funkcji
main
dodaj wywołanie metodydescribe
na instancji prostokąta. Uruchom program i zobacz wynik. - Metodę domyślną możesz nadpisać w strukturze. Dopisz implementację metody
describe
w strukturzeRectangle
.impl Shape for Rectangle { ... fn describe(&self) { println!("I'm a rectangle."); } }
- Uruchom ponownie kod i zobacz rezultat.
Implementacja cechy z innego modułu
Standardowa bibliotek Rust oraz biblioteki otwartoźródłowe dostarczają wiele cech, które mogą wzbogacić zachowanie naszych struktur. Przykładem jest tutaj cecha Display
, która pozwala zdefiniować sposób wypisywania struktury w terminalu.
- W tym samym pliku,
rectangle.rs
dodaj blok kodu, który zaimplementuję cechęDisplay
dla strukturyRectangle
impl Display for Rectangle { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { todo!() } }
- Na początku pliku dodaj instrukcję
use
, która zaimportuje typDisplay
iFormatter
do bieżącego kontekstu (materiał o modułach i użyciu typów z innych modułów znajdziesz w kolejnych rozdziałach).use std::fmt::{Display, Formatter};
- Zaimplementuj metodę
fmt
tak aby wypisała informacje o wysokości i szerokości prostokąta. Do wypisywania zawartości wykorzystaj makrowrite!
. Przykładowa implementacja:fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Rectangle[x={},y={}]", self.x, self.y)?; Ok(()) }
- W funkcji
main
wypisz informacje o prostokącie (zmiennejr
) dodając następującą linię kodu:println!("{}", r);
Dzięki implementacji cechy Display
dla struktury prostokąta, można wypisać informacje o instancji struktury używająć nawiasów klamrowych ({}
).