Menu
Coddy logo textTech

Обобщённые структуры

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

До сих пор каждая созданная вами структура имела поля с конкретными типами. У Pet есть имя типа String, у Rectangle есть размеры типа f64. Но что, если вы хотите создать структуру, которая может содержать данные любого типа?

Именно здесь на помощь приходят обобщения (generics). Обобщения позволяют писать гибкий, повторно используемый код, который работает с множеством типов. Вместо жесткого кодирования конкретного типа вы используете заполнитель — обычно T — который заменяется конкретным типом при использовании структуры.

Вот простая структура-обертка, которая может содержать любой тип:

struct Wrapper<T> {
    value: T,
}

<T> после имени struct объявляет параметр обобщенного типа. Внутри struct T служит заполнителем для любого типа, который вы будете использовать на самом деле. При создании экземпляра Rust выводит конкретный тип:

let int_wrapper = Wrapper { value: 42 };        // T — это i32
let float_wrapper = Wrapper { value: 3.14 };    // T — это f64
let text_wrapper = Wrapper { value: "hello" };  // T — это &str

Каждый из них является отдельным конкретным типом — Wrapper<i32>, Wrapper<f64> и Wrapper<&str> — но все они используют одно и то же определение структуры. Это избавляет от необходимости писать отдельные структуры IntWrapper, FloatWrapper и StringWrapper, которые делают по сути одно и то же.

Буква T — это просто соглашение (сокращение от «Type»). Вы можете использовать любой допустимый идентификатор, но T является стандартным в Rust и большинстве других языков с поддержкой обобщений.

challenge icon

Задание

Легко

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

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

  • container.rs: Определите публичную универсальную структуру с именем Container<T> с одним публичным полем item типа T. Этот контейнер должен иметь возможность хранить любой тип — целые числа, числа с плавающей запятой, строки или что-либо еще.
  • main.rs: Подключите модуль контейнера и продемонстрируйте его гибкость, создав контейнеры, содержащие различные типы данных. Вы создадите три контейнера и выведете содержимое каждого из них.

В основном файле создайте следующие контейнеры:

  1. Контейнер, содержащий целое число (первый ввод, преобразованный в i32)
  2. Контейнер, содержащий число с плавающей запятой (второй ввод, преобразованный в f64)
  3. Контейнер, содержащий строку (третий ввод, сохраненный как String)

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

Integer container: {value}
Float container: {value}
String container: {value}

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

Integer container: 42
Float container: 3.14
String container: hello

Обратите внимание, как одно и то же определение Container<T> работает для всех трех различных типов — в этом и заключается сила обобщений!

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

Шпаргалка

Обобщения (Generics) позволяют создавать структуры, которые могут хранить данные любого типа, используя заполнитель параметра типа.

Чтобы объявить обобщенную структуру, используйте угловые скобки с параметром типа (по соглашению это T):

struct Wrapper<T> {
    value: T,
}

<T> объявляет параметр обобщенного типа, а T внутри структуры выступает в качестве заполнителя для фактического типа.

При создании экземпляров Rust выводит конкретный тип:

let int_wrapper = Wrapper { value: 42 };        // T — это i32
let float_wrapper = Wrapper { value: 3.14 };    // T — это f64
let text_wrapper = Wrapper { value: "hello" };  // T — это &str

Каждый экземпляр становится отдельным конкретным типом (Wrapper<i32>, Wrapper<f64>, Wrapper<&str>), при этом используя одно и то же определение структуры.

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

mod container;

use container::Container;

fn main() {
    // Прочитать три входных значения
    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: Создать три контейнера, используя Container<T>
    // 1. Контейнер для целого числа, содержащий int_value
    // 2. Контейнер для числа с плавающей запятой, содержащий float_value
    // 3. Контейнер для строки, содержащий string_value

    // TODO: Вывести содержимое каждого контейнера в требуемом формате
    // Integer container: {value}
    // Float container: {value}
    // String container: {value}
}
quiz iconПроверьте себя

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

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