Множественные ограничения
Часть раздела Object Oriented Programming путешествия по Rust на Coddy — урок 41 из 61.
Иногда одного ограничения типажа недостаточно. Вам может понадобиться обобщённый тип, который можно и вывести на печать, и получить по нему сводку. Rust позволяет требовать несколько типажей, используя синтаксис +.
Вот как указать, что тип должен реализовывать два типажа:
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
Ограничение T: Display + Summary означает, что «T должен реализовывать как Display, так и Summary». Внутри функции вы можете использовать возможности обоих типажей — вывод с помощью {} (из Display) и вызов summarize() (из Summary).
Вы можете объединять в цепочку столько типажей, сколько необходимо:
fn process<T: Display + Summary + Clone>(item: T) {
// Можно выводить на печать, суммировать И клонировать
}
Этот паттерн необходим, когда ваша функция полагается на несколько вариантов поведения. Вместо того чтобы принимать любой тип и надеяться, что он сработает, вы явно объявляете, какие именно возможности требуются, — и компилятор проверяет это на этапе компиляции.
Задание
ЛегкоДавайте создадим систему инспекции продуктов, которая требует от предметов наличия нескольких возможностей! Вы создадите универсальную функцию, которая принимает только типы, реализующие как пользовательский типаж (trait), так и стандартный, демонстрируя, как синтаксис + объединяет несколько ограничений.
Вы организуете свой код в двух файлах:
product.rs: Определите публичный типажInspectableс методомinspect, который принимает&selfи возвращаетString, содержащую детали инспекции. Затем создайте публичную структуруGadgetс публичными полямиname(String) иserial(u32). Ваш Gadget должен реализовывать какInspectable(возвращая"Inspecting: {}"с именем), так иstd::fmt::Display(форматируя как"{} (SN: {})"). Наконец, создайте публичную универсальную функциюfull_report, которая принимает любой типT, реализующий одновременноDisplayиInspectable. Эта функция должна выводить две строки: сначала сам предмет, используя форматтер{}, затем результат вызоваinspect().main.rs: Подключите модуль product и создайте экземплярGadget, используя предоставленные входные данные. Вызовитеfull_reportсо своим гаджетом, чтобы показать, что он удовлетворяет требованиям обоих типажей.
Сила множественных ограничений заключается в том, что ваша функция full_report может использовать возможности обоих типажей — красиво отображать предмет И получать детали инспекции — и все это гарантируется на этапе компиляции.
Ваш вывод должен показывать как формат отображения, так и результат инспекции:
{name} (SN: {serial})
Inspecting: {name}Например, при входных данных Smartwatch и 98765:
Smartwatch (SN: 98765)
Inspecting: SmartwatchВы получите два входных значения: название гаджета и серийный номер (преобразуйте в u32).
Шпаргалка
Вы можете указать несколько ограничений типажа (trait bounds) для универсального типа, используя синтаксис +:
fn function_name<T: Trait1 + Trait2>(item: T) {
// Можно использовать возможности обоих типажей
}
Пример с Display и пользовательским типажом:
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
Вы можете объединять в цепочку столько типажей, сколько необходимо:
fn process<T: Display + Summary + Clone>(item: T) {
// Можно выводить на печать, суммировать И клонировать
}
Компилятор гарантирует, что тип реализует все указанные типажи на этапе компиляции.
Попробуйте сами
mod product;
use product::{Gadget, full_report};
fn main() {
// Чтение входных данных
let mut name = String::new();
std::io::stdin().read_line(&mut name).expect("Failed to read line");
let name = name.trim().to_string();
let mut serial_input = String::new();
std::io::stdin().read_line(&mut serial_input).expect("Failed to read line");
let serial: u32 = serial_input.trim().parse().expect("Failed to parse serial");
// TODO: Создайте экземпляр Gadget с именем и серийным номером
// TODO: Вызовите full_report с вашим гаджетом
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media Player