Menu
Coddy logo textTech

Векторы трейтов

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

Вы видели, как Box<dyn Trait> позволяет одной переменной хранить разные типы. Настоящая мощь проявляется, когда вы комбинируете это с коллекциями — хранением нескольких различных типов в одном векторе.

Обычный вектор требует, чтобы все элементы были одного типа. У вас не может быть Vec<Dog>, который также содержит кошек. Но с помощью объектов-типажей вы можете создать вектор, который содержит что угодно, реализующее общий типаж:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;
struct Bird;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

impl Speak for Bird {
    fn speak(&self) -> String { String::from("Tweet!") }
}

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
        Box::new(Bird),
    ];
}

Тип Vec<Box<dyn Speak>> означает «вектор упакованных (boxed) объектов-типажей». Каждый элемент — это Box, указывающий на что-то, что реализует Speak, — фактические типы могут различаться. Вы оборачиваете каждое значение с помощью Box::new() при добавлении его в вектор.

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

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, которая может обрабатывать различные типы оповещений! Вы создадите коллекцию, в которой хранятся различные типы уведомлений — электронные письма, SMS-сообщения и push-уведомления — все в одном векторе с использованием объектов типажа (trait objects).

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

  • notifications.rs: Определите публичный типаж Notify с методом send, который принимает &self и возвращает String, описывающую отправляемое уведомление. Затем создайте три публичные структуры:
    • Email — с публичным полем recipient (String). Его метод send должен возвращать "Email to: {recipient}"
    • Sms — с публичным полем phone (String). Его метод send должен возвращать "SMS to: {phone}"
    • Push — с публичным полем device (String). Его метод send должен возвращать "Push to: {device}"
  • main.rs: Подключите модуль notifications и создайте вектор типа Vec<Box<dyn Notify>>, который содержит все три типа уведомлений. Используйте предоставленные входные данные для создания по одному экземпляру каждого типа уведомления, добавьте их в вектор по порядку (Email, Sms, Push), а затем выведите результат вызова send() для первого элемента вектора.

Ключевая концепция здесь заключается в том, что, несмотря на то что Email, Sms и Push являются совершенно разными структурами, они все могут находиться в одном векторе, так как они реализуют типаж Notify. Каждый элемент оборачивается в Box::new() для создания объекта типажа.

Ваш вывод должен показать первое уведомление:

Email to: {recipient}

Например, при входных данных "alice@example.com", "555-1234" и "iPhone-12":

Email to: alice@example.com

Вы получите три входных значения: получателя электронного письма, номер телефона и название устройства.

Шпаргалка

Vec<Box<dyn Trait>> позволяет хранить несколько различных типов в одном векторе, при условии, что все они реализуют один и тот же типаж (trait).

Обычные векторы требуют, чтобы все элементы были одного типа, но объекты-типажи (trait objects) позволяют создавать гетерогенные коллекции:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
    ];
}

При добавлении в вектор каждый элемент оборачивается в Box::new(). Фактические типы могут отличаться, если они реализуют общий типаж.

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

mod notifications;

use notifications::{Notify, Email, Sms, Push};

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

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

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

    // TODO: Создать Vec<Box<dyn Notify>> для хранения различных типов уведомлений
    // TODO: Создать экземпляры Email, Sms и Push, используя входные данные
    // TODO: Добавить их в вектор по порядку (Email, Sms, Push), используя Box::new()
    // TODO: Вывести результат вызова send() для первого элемента
}
quiz iconПроверьте себя

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

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