Узагальнені типи, трейти та часи життя
Кожна мова програмування має інструменти для ефективної обробки дублювання понять. У Rust одним із таких інструментів є узагальнення (generics): абстрактні замінники для конкретних типів або інших властивостей. Ми можемо виразити поведінку узагальнень (generics) або те, як вони пов’язані з іншими узагальненнями (generics), не знаючи, що буде на їхньому місці під час компіляції та виконання коду.
Функції можуть приймати параметри деякого узагальненого типу, замість конкретного типу
на кшталт i32 або String, так само як вони приймають параметри з невідомими
значеннями, щоб запускати той самий код на кількох конкретних значеннях. Насправді ми вже
використовували узагальнення (generics) у Розділі 6 з Option<T>, у Розділі 8 з Vec<T> і
HashMap<K, V>, а також у Розділі 9 з Result<T, E>. У цьому розділі ви
дізнаєтеся, як визначати власні типи, функції та методи з узагальненнями (generics)!
Спочатку ми переглянемо, як виділити функцію, щоб зменшити дублювання коду. Потім ми використаємо ту саму техніку, щоб створити узагальнену функцію з двох функцій, які відрізняються лише типами своїх параметрів. Ми також пояснимо, як використовувати узагальнені типи у визначеннях структур і переліків.
Потім ви дізнаєтеся, як використовувати трейти для визначення поведінки узагальненим способом. Ви можете поєднувати трейти з узагальненими типами, щоб обмежити узагальнений тип приймати лише ті типи, які мають певну поведінку, на відміну від будь-якого типу.
Нарешті, ми обговоримо часи життя: різновид узагальнень (generics), який надає компілятору інформацію про те, як посилання пов’язані одне з одним. Часи життя дозволяють нам дати компілятору достатньо інформації про запозичені значення, щоб він міг гарантувати, що посилання будуть дійсними в більшій кількості ситуацій, ніж це було б без нашої допомоги.
Усунення дублювання шляхом виділення функції
Узагальнення (generics) дозволяють нам замінювати конкретні типи заповнювачем, який представляє кілька типів, щоб усунути дублювання коду. Перш ніж занурюватися в синтаксис узагальнень (generics), спочатку подивімося, як усунути дублювання способом, що не передбачає узагальнені типи, шляхом виділення функції, яка замінює конкретні значення заповнювачем, що представляє кілька значень. Потім ми застосуємо ту саму техніку, щоб виділити узагальнену функцію! Розглядаючи, як розпізнати дубльований код, який можна виділити у функцію, ви почнете розпізнавати дубльований код, який може використовувати узагальнення (generics).
Ми почнемо з короткої програми в Лістингу 10-1, яка знаходить найбільше число в списку.
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
assert_eq!(*largest, 100);
}
Ми зберігаємо список цілих чисел у змінній number_list і поміщаємо посилання
на перше число в списку у змінну з назвою largest. Потім ми ітеруємося
через усі числа в списку, і якщо поточне число більше за число, збережене в largest,
ми замінюємо посилання в цій змінній. Однак якщо поточне число менше або дорівнює
найбільшому числу, побаченому досі, змінна не змінюється, і код переходить до наступного числа
в списку. Після розгляду всіх чисел у списку largest має
посилатися на найбільше число, яким у цьому випадку є 100.
Тепер перед нами поставлено завдання знайти найбільше число у двох різних списках чисел. Щоб зробити це, ми можемо вирішити дублювати код у Лістингу 10-1 і використовувати ту саму логіку у двох різних місцях програми, як показано в Лістингу 10-2.
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
}
Хоча цей код працює, дублювання коду є виснажливим і схильним до помилок. Нам також доводиться пам’ятати про оновлення коду в кількох місцях, коли ми хочемо його змінити.
Щоб усунути це дублювання, ми створимо абстракцію, визначивши функцію, яка працює з будь-яким списком цілих чисел, переданим як параметр. Це рішення робить наш код зрозумілішим і дозволяє нам виразити поняття пошуку найбільшого числа в списку абстрактно.
У Лістингу 10-3 ми виділяємо код, який знаходить найбільше число, у
функцію з назвою largest. Потім ми викликаємо цю функцію, щоб знайти найбільше число
у двох списках з Лістингу 10-2. Ми також могли б використати цю функцію для будь-якого іншого
списку значень i32, який у нас може з’явитися в майбутньому.
fn largest(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {result}");
assert_eq!(*result, 100);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let result = largest(&number_list);
println!("The largest number is {result}");
assert_eq!(*result, 6000);
}
Функція largest має параметр під назвою list, який представляє будь-який
конкретний зріз (slice) значень i32, який ми можемо передати у функцію. У результаті,
коли ми викликаємо функцію, код виконується на конкретних значеннях, які ми передаємо
в неї.
Підсумовуючи, ось кроки, які ми виконали, щоб змінити код з Лістингу 10-2 на Лістинг 10-3:
- Визначте дубльований код.
- Виділіть дубльований код у тіло функції та вкажіть вхідні дані й значення, що повертаються цим кодом, у сигнатурі функції.
- Оновіть два випадки дубльованого коду, щоб замість нього викликати функцію.
Далі ми використаємо ті самі кроки з узагальненнями (generics), щоб зменшити дублювання коду. У
той самий спосіб, у який тіло функції може працювати з абстрактним list
замість конкретних значень, узагальнення (generics) дозволяють коду працювати з абстрактними типами.
Наприклад, припустімо, що в нас є дві функції: одна, яка знаходить найбільший елемент у
зрізі значень i32, і одна, яка знаходить найбільший елемент у зрізі значень char.
Як би ми усунули це дублювання? Давайте з’ясуємо!