Menu
Coddy logo textTech

Обобщённые функции

Часть раздела Object Oriented Programming путешествия по Rust на Coddy — урок 27 из 61.

Обобщения не ограничиваются структурами — вы также можете писать отдельные функции, которые работают с любым типом. Это полезно, когда вам нужна вспомогательная функция, которая не принадлежит конкретной структуре, но при этом должна оставаться гибкой.

Синтаксис повторяет то, что вы уже видели в структурах. Объявите универсальный параметр в угловых скобках после имени функции, а затем используйте его в параметрах и типе возвращаемого значения:

fn identity<T>(value: T) -> T {
    value
}

Эта функция identity принимает значение любого типа и возвращает его без изменений. <T> объявляет универсальный параметр (generic), value: T принимает его в качестве параметра, а -> T указывает тип возвращаемого значения. При вызове функции Rust выводит конкретный тип:

let num = identity(42);        // T — это i32
let text = identity("hello");  // T — это &str

Вы также можете использовать несколько универсальных параметров в функциях, точно так же, как и со структурами:

fn make_pair<T, U>(first: T, second: U) -> (T, U) {
    (first, second)
}

let pair = make_pair(10, "ten");  // возвращает (i32, &str)

Обобщенные функции позволяют написать переиспользуемую логику один раз и применять её ко многим типам, сокращая дублирование кода и сохраняя при этом типобезопасность.

challenge icon

Задание

Легко

Давайте создадим модуль утилит с универсальными (generic) функциями, которые могут работать с любым типом! Вы создадите отдельные функции, которые продемонстрируют, как обобщения делают ваш код гибким и переиспользуемым, не привязываясь к конкретной структуре.

Вы организуете свой код в двух файлах:

  • utils.rs: Создайте коллекцию публичных универсальных функций-утилит:
    • wrap_in_pair<T> — принимает одно значение и возвращает кортеж, содержащий это значение дважды: (value, value). Это требует, чтобы тип реализовывал трейт Clone, поэтому используйте <T: Clone>
    • swap<T, U> — принимает два значения потенциально разных типов и возвращает их в обратном порядке в виде кортежа (U, T)
  • main.rs: Импортируйте ваш модуль утилит и продемонстрируйте работу этих универсальных функций с разными типами. Покажите, как одни и те же определения функций беспрепятственно обрабатывают целые числа, числа с плавающей запятой и строки.

В основном файле продемонстрируйте работу ваших функций-утилит следующим образом:

  1. Используйте wrap_in_pair с целым числом (первый ввод, преобразованный в i32) и выведите оба элемента
  2. Используйте wrap_in_pair со строкой (второй ввод) и выведите оба элемента
  3. Используйте swap с целым числом (третий ввод, преобразованный в i32) и строкой (четвертый ввод), затем выведите результат перестановки

Ваш вывод должен соответствовать следующему формату:

Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})

Например, при входных данных 5, hello, 42 и world:

Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)

Обратите внимание, как wrap_in_pair одинаково работает как для целых чисел, так и для строк, а swap обрабатывает два совершенно разных типа — в этом и заключается гибкость универсальных функций!

Вы получите четыре входных значения: целое число, строку, еще одно целое число и еще одну строку.

Шпаргалка

Обобщенные функции позволяют писать автономные вспомогательные функции, которые работают с любым типом. Объявите обобщенный параметр в угловых скобках после имени функции:

fn identity<T>(value: T) -> T {
    value
}

Синтаксис <T> объявляет параметр обобщенного типа, который затем можно использовать в параметрах функции и возвращаемых типах. Rust выводит конкретный тип при вызове функции:

let num = identity(42);        // T is i32
let text = identity("hello");  // T is &str

В одной функции можно использовать несколько обобщенных параметров:

fn make_pair<T, U>(first: T, second: U) -> (T, U) {
    (first, second)
}

let pair = make_pair(10, "ten");  // returns (i32, &str)

Когда обобщенному типу требуются определенные возможности, используйте ограничения типажа (trait bounds):

fn duplicate<T: Clone>(value: T) -> (T, T) {
    (value.clone(), value)
}

Попробуйте сами

mod utils;

use std::io;

fn main() {
    // Чтение четырех входных значений
    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: Используйте wrap_in_pair с num1 и выведите результат
    // Формат: Pair of ints: ({value}, {value})

    // TODO: Используйте wrap_in_pair с str1 и выведите результат
    // Формат: Pair of strings: ({value}, {value})

    // TODO: Используйте swap с num2 и str2, затем выведите результат обмена
    // Формат: Swapped: ({string}, {int})
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming