Funkcje generyczne
Część sekcji Programowanie obiektowe ścieżki Rust w Coddy. Lekcja 27 z 61.
Generyki nie ograniczają się do struktur. Możesz również pisać samodzielne funkcje, które działają z dowolnym typem. Jest to przydatne, gdy potrzebujesz funkcji pomocniczej, która nie należy do konkretnej struktury, ale powinna być elastyczna.
Składnia przypomina tę, którą znasz już ze struktur. Zadeklaruj parametr generyczny w nawiasach ostrych po nazwie funkcji, a następnie użyj go w parametrach i typie zwracanym:
fn identity<T>(value: T) -> T {
value
}
Ta funkcja identity przyjmuje wartość dowolnego typu i zwraca ją bez zmian. <T> deklaruje parametr generyczny, value: T przyjmuje go jako parametr, a -> T określa typ zwracany. Gdy wywołujesz funkcję, Rust wnioskuje konkretny typ:
let num = identity(42); // T jest i32
let text = identity("hello"); // T jest &str
Możesz także używać wielu parametrów generycznych w funkcjach, tak jak w strukturach:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // zwraca (i32, &str)
Funkcje generyczne pozwalają napisać logikę wielokrotnego użytku raz i stosować ją do wielu typów, ograniczając powielanie kodu przy zachowaniu bezpieczeństwa typów.
Wyzwanie
ŁatwyZbudujmy moduł narzędziowy z funkcjami generycznymi, które mogą działać z dowolnym typem! Utworzysz samodzielne funkcje pokazujące, jak generyczność sprawia, że Twój kod jest elastyczny i wielokrotnego użytku, bez powiązania z konkretną strukturą.
Rozmieścisz kod w dwóch plikach:
utils.rs: Utwórz zestaw publicznych generycznych funkcji narzędziowych:wrap_in_pair<T>: przyjmuje pojedynczą wartość i zwraca krotkę zawierającą tę wartość dwukrotnie:(value, value). Wymaga to, aby typ implementował cechęClone, więc użyj<T: Clone>swap<T, U>: przyjmuje dwie wartości, które mogą mieć różne typy, i zwraca je w odwrotnej kolejności jako krotkę(U, T)
main.rs: Zaimportuj moduł narzędziowy i pokaż działanie tych funkcji generycznych na różnych typach. Pokaż, jak te same definicje funkcji bezproblemowo obsługują liczby całkowite, liczby zmiennoprzecinkowe i ciągi znaków.
W pliku głównym pokaż działanie funkcji narzędziowych, wykonując następujące czynności:
- Użyj
wrap_in_pairz liczbą całkowitą (pierwsze wejście, sparsowane jakoi32) i wypisz oba elementy - Użyj
wrap_in_pairz ciągiem znaków (drugie wejście) i wypisz oba elementy - Użyj
swapz liczbą całkowitą (trzecie wejście, sparsowane jakoi32) i ciągiem znaków (czwarte wejście), a następnie wypisz wynik zamiany kolejności
Wynik powinien mieć następujący format:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})Na przykład dla wejść 5, hello, 42 i world:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Zwróć uwagę, że wrap_in_pair działa identycznie zarówno dla liczb całkowitych, jak i ciągów znaków, a swap obsługuje dwa zupełnie różne typy: na tym polega elastyczność funkcji generycznych!
Otrzymasz cztery wejścia: liczbę całkowitą, ciąg znaków, kolejną liczbę całkowitą i kolejny ciąg znaków.
Spróbuj swoich sił
mod utils;
use std::io;
fn main() {
// Wczytaj cztery dane wejściowe
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: Użyj wrap_in_pair z num1 i wypisz wynik
// Format: Pair of ints: ({value}, {value})
// TODO: Użyj wrap_in_pair z str1 i wypisz wynik
// Format: Pair of strings: ({value}, {value})
// TODO: Użyj swap z num2 i str2, a następnie wypisz wynik zamiany
// Format: Swapped: ({string}, {int})
}
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