Struktury generyczne
Część sekcji Programowanie obiektowe ścieżki Rust w Coddy. Lekcja 24 z 61.
Jak dotąd każda utworzona przez Ciebie struktura miała pola o konkretnych, określonych typach. Pet ma nazwę typu String, a Rectangle ma wymiary typu f64. A co, jeśli chcesz utworzyć strukturę, która może przechowywać dowolny typ danych?
Właśnie tutaj przydają się typy generyczne. Pozwalają pisać elastyczny, wielokrotnego użytku kod, który działa z wieloma typami. Zamiast wpisywać na sztywno konkretny typ, używasz symbolu zastępczego, zazwyczaj T, który zostaje zastąpiony konkretnym typem, gdy używasz struktury.
Oto prosty opakowujący typ strukturalny, który może przechowywać dowolny typ:
struct Wrapper<T> {
value: T,
}
<T> po nazwie struktury deklaruje parametr typu generycznego. Wewnątrz struktury T pełni rolę zastępnika dla typu, którego faktycznie użyjesz. Gdy tworzysz instancję, Rust wywnioskuje konkretny typ:
let int_wrapper = Wrapper { value: 42 }; // T jest i32
let float_wrapper = Wrapper { value: 3.14 }; // T jest f64
let text_wrapper = Wrapper { value: "hello" }; // T jest &str
Każdy z nich jest innym typem konkretnym: Wrapper<i32>, Wrapper<f64> i Wrapper<&str>, ale wszystkie korzystają z tej samej definicji struktury. Dzięki temu nie trzeba pisać osobnych struktur IntWrapper, FloatWrapper i StringWrapper, które zasadniczo robią to samo.
Litera T to tylko konwencja (skrót od „Type”). Możesz użyć dowolnego prawidłowego identyfikatora, ale T jest standardem w Rust i większości innych języków obsługujących typy generyczne.
Wyzwanie
ŁatwyZbudujmy generyczny kontener, który może przechowywać wartość dowolnego typu! Utworzysz strukturę Container, która pokazuje, jak generyczność sprawia, że kod jest elastyczny i można go ponownie wykorzystać.
Rozmieścisz kod w dwóch plikach:
container.rs: Zdefiniuj publiczną generyczną strukturę o nazwieContainer<T>z jednym publicznym polemitemtypuT. Ten kontener powinien móc przechowywać dowolny typ: liczby całkowite, liczby zmiennoprzecinkowe, ciągi znaków lub cokolwiek innego.main.rs: Dołącz moduł kontenera i pokaż jego elastyczność, tworząc kontenery przechowujące różne typy danych. Utworzysz trzy kontenery i wyświetlisz, co zawiera każdy z nich.
W pliku głównym utwórz następujące kontenery:
- Kontener przechowujący wartość całkowitą (pierwsze wejście, sparsowane jako
i32) - Kontener przechowujący wartość zmiennoprzecinkową (drugie wejście, sparsowane jako
f64) - Kontener przechowujący ciąg znaków (trzecie wejście, zachowane jako
String)
Wynik powinien wyświetlać zawartość każdego kontenera w następującym formacie:
Integer container: {value}
Float container: {value}
String container: {value}Na przykład dla wejść 42, 3.14 i hello:
Integer container: 42
Float container: 3.14
String container: helloZauważ, że ta sama definicja Container<T> działa dla wszystkich trzech różnych typów: na tym polega siła generyczności!
Otrzymasz trzy dane wejściowe: wartość całkowitą, wartość zmiennoprzecinkową i ciąg znaków.
Spróbuj swoich sił
mod container;
use container::Container;
fn main() {
// Odczytaj trzy dane wejściowe
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let float_value: f64 = input2.trim().parse().expect("Failed to parse float");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let string_value = input3.trim().to_string();
// TODO: Utwórz trzy kontenery przy użyciu Container<T>
// 1. Kontener na liczbę całkowitą przechowujący int_value
// 2. Kontener na liczbę zmiennoprzecinkową przechowujący float_value
// 3. Kontener na ciąg znaków przechowujący string_value
// TODO: Wypisz zawartość każdego kontenera w wymaganym formacie
// Integer container: {value}
// Float container: {value}
// String container: {value}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Metody i zachowanie
Wprowadzenie do bloków implementacjiParametr selfMetody modyfikująceFunkcje powiązaneWiele bloków implementacjiŁańcuchowe wywoływanie metodPowtórzenie — działania na prostokącie4Projekt: Wirtualne zwierzątko
Definiowanie zwierzątkaKarmienie zwierzątka7Standardowe cechy
Cecha DebugCecha DisplayClone i CopyCechy równościPowtórka – punkt do wydrukowania10Projekt: System dokumentów
Trait DrawKomponent tekstowy2Hermetyzacja i moduły
Podstawy modułówSłowo kluczowe publicPola prywatneGetterySetteryPodsumowanie — bezpieczna skrytka5Typy generyczne
Struktury generyczneMetody generyczneWiele typów generycznychFunkcje generycznePodsumowanie – punkt współrzędnych8Cechy jako ograniczenia
Składnia ograniczeń cechWiele ograniczeńKlauzula whereZwracanie typów z użyciem cechPodsumowanie — generyczna drukarka11Wzorce projektowe w Rust
Wzorzec NewtypeKompozycjaTrait DropFrom i IntoPodsumowanie — atrapowy inteligentny wskaźnik3Zaawansowane typy wyliczeniowe
Typy wyliczeniowe z danymiMetody typów wyliczeniowychDopasowywanie wariantów danychTyp wyliczeniowy Option — powtórzeniePodsumowanie — typ wyliczeniowy Shape6Definiowanie cech
Czym jest cecha?Implementowanie cechImplementacje domyślneNadpisywanie wartości domyślnychCechy z parametramiPowtórzenie — odtwarzacz multimedialnyPoćwicz samodzielnie: Kompilator Rust online