Menu
Coddy logo textTech

Итерация по трейт-объектам

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

Теперь, когда вы можете хранить различные типы в Vec<Box<dyn Trait>>, следующим шагом будет обработка этой коллекции. Итерация по объектам-типажам работает точно так же, как и итерация по любому вектору — вы перебираете каждый элемент и вызываете методы, определенные типажом.

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
        Box::new(Circle { radius: 1.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

Когда вы выполняете итерацию с помощью for shape in &shapes, каждый shape является ссылкой на Box<dyn Shape>. Вы можете вызывать любой метод трейта Shape напрямую — Rust автоматически обрабатывает динамическую диспетчеризацию, вызывая правильную реализацию для каждого конкретного типа.

Этот паттерн эффективен для агрегирования результатов. Вы можете суммировать все площади, фильтровать фигуры по размеру или трансформировать коллекцию. Ключевая идея заключается в том, что как только элементы попадают в коллекцию объектов типажа (trait objects), вы взаимодействуете с ними исключительно через интерфейс типажа, независимо от их исходных типов.

challenge icon

Задание

Легко

Давайте создадим калькулятор цен для корзины покупок, которая может содержать различные типы товаров! Вы создадите систему, в которой продукты и услуги — каждый со своей логикой ценообразования — могут храниться вместе и обрабатываться для расчета общей стоимости.

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

  • items.rs: Определите публичный типаж (trait) Priceable с методом price, который принимает &self и возвращает f64. Затем создайте две публичные структуры:
    • Product — с публичными полями name (String) и cost (f64). Его метод price должен просто возвращать cost.
    • Service — с публичными полями name (String), hourly_rate (f64) и hours (f64). Его метод price должен возвращать hourly_rate * hours.
  • main.rs: Подключите модуль items и создайте вектор типа Vec<Box<dyn Priceable>>. Используйте входные данные для создания одного Product и одного Service, добавьте их в свой вектор, затем переберите коллекцию, чтобы рассчитать и вывести общую стоимость всех элементов.

Здесь проявляется мощь объектов типажей (trait objects) — вашему коду итерации не нужно знать, обрабатывает ли он Product или Service. Он просто вызывает price() для каждого элемента, и Rust автоматически переходит к правильной реализации.

Ваш вывод должен отображать общую цену с одним десятичным знаком:

Total: ${total}

Например, при входных данных Laptop, 999.99, Consulting, 150.0 и 3.0:

Total: $1449.99

И при входных данных Book, 29.99, Tutoring, 50.0 и 2.0:

Total: $129.99

Вы получите пять входных значений: название продукта, стоимость продукта, название услуги, почасовую ставку и количество отработанных часов (анализируйте числовые значения как f64).

Шпаргалка

Чтобы перебрать коллекцию объектов типажа, используйте стандартный цикл for со ссылкой на вектор:

for item in &collection {
    item.trait_method();
}

При итерации с помощью for item in &vec каждый item является ссылкой на Box<dyn Trait>. Вы можете вызывать любой метод типажа напрямую — Rust автоматически обрабатывает динамическую диспетчеризацию, вызывая правильную реализацию для каждого конкретного типа.

Пример итерации по объектам типажа для агрегирования результатов:

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

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

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

mod items;

use items::{Priceable, Product, Service};

fn main() {
    // Чтение входных данных
    let mut product_name = String::new();
    std::io::stdin().read_line(&mut product_name).expect("Failed to read line");
    let product_name = product_name.trim().to_string();

    let mut product_cost = String::new();
    std::io::stdin().read_line(&mut product_cost).expect("Failed to read line");
    let product_cost: f64 = product_cost.trim().parse().expect("Failed to parse");

    let mut service_name = String::new();
    std::io::stdin().read_line(&mut service_name).expect("Failed to read line");
    let service_name = service_name.trim().to_string();

    let mut hourly_rate = String::new();
    std::io::stdin().read_line(&mut hourly_rate).expect("Failed to read line");
    let hourly_rate: f64 = hourly_rate.trim().parse().expect("Failed to parse");

    let mut hours = String::new();
    std::io::stdin().read_line(&mut hours).expect("Failed to read line");
    let hours: f64 = hours.trim().parse().expect("Failed to parse");

    // TODO: Создайте Product, используя product_name и product_cost

    // TODO: Создайте Service, используя service_name, hourly_rate и hours

    // TODO: Создайте Vec<Box<dyn Priceable>> и добавьте в него оба элемента

    // TODO: Пройдите циклом по вектору и вычислите общую стоимость

    // TODO: Выведите общую стоимость с одним десятичным знаком
    // Формат: Total: ${total:.1}
}
quiz iconПроверьте себя

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

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