Синтаксис ограничений трейтов
Часть раздела Object Oriented Programming путешествия по Rust на Coddy — урок 40 из 61.
До сих пор вы учились определять типажи и реализовывать их для структур. Вы также работали с обобщёнными типами для написания гибкого кода. Теперь пришло время объединить эти концепции — использовать типажи, чтобы ограничить типы, которые может принимать обобщённая функция.
Рассмотрим обобщённую функцию, которой необходимо вызвать определённый метод у своего параметра. Без каких-либо ограничений у Rust нет гарантии, что тип T обладает этим методом:
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // Ошибка! У T может не быть метода summarize()
}
Решением является ограничение типажом (trait bound). Добавив : TraitName после универсального параметра, вы сообщаете Rust, что T должен реализовывать этот типаж:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize()); // Теперь Rust знает, что этот метод существует
}
Синтаксис <T: Summary> читается как «T — это любой тип, реализующий Summary». Теперь функция принимает только типы, обладающие требуемым поведением. Если вы попытаетесь передать тип, который не реализует Summary, компилятор отклонит его с понятной ошибкой.
Этот паттерн мощен, потому что он позволяет писать обобщенный код, который при этом имеет доступ к специфической функциональности. Ваша функция остается гибкой — она работает с любым типом — но только с теми типами, которые обеспечивают необходимое вам поведение.
Задание
ЛегкоДавайте создадим систему уведомлений, которая использует ограничения типажей (trait bounds), чтобы гарантировать отправку только правильно отформатированных сообщений! Вы создадите универсальную функцию, которая принимает любой тип, реализующий определенный типаж, демонстрируя, как ограничения типажей ограничивают параметры обобщений.
Вы организуете свой код в двух файлах:
notification.rs: Определите публичный типажNotifyс методомmessage, который принимает&selfи возвращаетString. Затем создайте две публичные структуры, реализующие этот типаж:Email— с публичным полемsubject(String). Его методmessageдолжен возвращать"Email: {subject}"SMS— с публичным полемcontent(String). Его методmessageдолжен возвращать"SMS: {content}"
send_notification, которая принимает любой типT, реализующий типажNotify. Эта функция должна выводить на печать результат вызоваmessage()для данного элемента.main.rs: Подключите модуль уведомлений и используйте предоставленные входные данные для создания экземпляровEmailиSMS. Вызовитеsend_notificationдля каждого из них, чтобы продемонстрировать, что ваша универсальная функция работает с любым типом, реализующимNotify.
Ключевая идея здесь заключается в том, что send_notification не знает конкретного типа, который она получает — она знает только то, что этот тип может сформировать сообщение. Ограничение типажа T: Notify гарантирует эту возможность.
Ваш вывод должен показывать отправку обоих уведомлений:
Email: {subject}
SMS: {content}Например, при входных данных Meeting Tomorrow и On my way!:
Email: Meeting Tomorrow
SMS: On my way!Вы получите два входных значения: тему электронного письма и содержание SMS.
Шпаргалка
Ограничение типажом (trait bound) ограничивает параметр универсального типа так, чтобы он принимал только типы, реализующие определенный типаж.
Синтаксис ограничений типажами использует : TraitName после параметра универсального типа:
fn function_name<T: TraitName>(item: T) {
// Теперь можно вызывать методы из TraitName для item
}
Пример с ограничением типажом:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize());
}
Запись <T: Summary> означает: «T — это любой тип, реализующий Summary». Это позволяет функции вызывать методы типажа, оставаясь при этом универсальной.
Попробуйте сами
mod notification;
use notification::{Email, SMS, send_notification};
fn main() {
// Чтение входных данных
let mut subject = String::new();
std::io::stdin().read_line(&mut subject).expect("Failed to read line");
let subject = subject.trim().to_string();
let mut content = String::new();
std::io::stdin().read_line(&mut content).expect("Failed to read line");
let content = content.trim().to_string();
// TODO: Создать экземпляр Email с темой
// TODO: Создать экземпляр SMS с содержимым
// TODO: Вызвать send_notification с email
// TODO: Вызвать send_notification с sms
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media Player