Menu
CoddyTech

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

Часть раздела Объектно-ориентированное программирование путешествия по 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 автоматически обрабатывает динамическую диспетчеризацию, вызывая правильную реализацию для каждого конкретного типа.

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

challenge icon

Задание

Легко

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

Ты распределишь код по двум файлам:

  • items.rs: Определи публичный трейт 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: Подключи модуль с элементами и создай вектор типа Vec<Box<dyn Priceable>>. Используй входные данные, чтобы создать один Product и один Service, добавь их в вектор, а затем пройдись по коллекции, чтобы вычислить и вывести общую стоимость всех элементов.

Именно здесь проявляется преимущество объектов трейтов. Код итерации не должен знать, обрабатывает ли он 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).

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

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Проверьте себя

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

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Rust