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
mainkod, który wywoła nowe metody na utworzynych instancja prostokątów. Sprawdź czy wszystko działa poprawnie.💡Zauważyłaś/eś, że struktura
Rectanglema 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ż typRectanglejest lokalny. Możemy również zaimplementowaćShapenaVec<T>w naszej skrzynce, ponieważ cechaShapejest lokalna.Nie możemy jednak implementować zewnętrznych cech na zewnętrznych typach. Na przykład, nie możemy zaimplementować cechy
DisplaynaVec<T>w naszej skrzynce, ponieważDisplayiVec<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
shapemetodędescribe:trait Shape { ... fn describe(&self) { println!("I'm a general shape."); } } - W funkcji
maindodaj wywołanie metodydescribena instancji prostokąta. Uruchom program i zobacz wynik. - Metodę domyślną możesz nadpisać w strukturze. Dopisz implementację metody
describew 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.rsdodaj blok kodu, który zaimplementuję cechęDisplaydla strukturyRectangleimpl Display for Rectangle { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { todo!() } } - Na początku pliku dodaj instrukcję
use, która zaimportuje typDisplayiFormatterdo 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ę
fmttak 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
mainwypisz 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 ({}).