Как и другие типы, трейты могут использоваться в качестве типа данных для параметров и возвращаемого результата функции.
Формальная установка трейта в качестве типа данных для параметра:
название_параметра: &impl название_трейта
После двоеточия идет оператор impl, а затем указывается название трейта.
Причем обычно передается не просто объект трейта, а ссылка на него, чтобы не менять владельца данных. Поэтому перед оператором impl указывается
амперсанд &
Например:
struct Person { name: String, age: u8} struct Message { title: String} trait Printer{ fn print(&self); } impl Printer for Person{ fn print(&self){ println!("Person {}; age: {}", self.name, self.age) } } impl Printer for Message{ fn print(&self){ println!("Message: {}", self.title) } } fn display(printable: &impl Printer) { printable.print(); } fn main(){ let tom = Person {name: String::from("Tom"), age: 36}; let hello = Message { title: String::from("Hello Rust")}; display(&tom); display(&hello); }
В данном случае имеются две структуры Person и Message. Они разные по своему характеру, представляют совершенно разные данные: одна структура представляет
человека, а другая - некоторое сообщение. Но мы хотим, чтобы эти структуры имели какой-то единый метод для вывода на консоль. Для этого определяется трейт
Printer
с одним методом print()
trait Printer{ fn print(&self); }
Каждая из структур по своему реализует этот метод.
Затем в функции display()
мы можем получить ссылку на объект трейта:
fn display(printable: &impl Printer) { printable.print(); }
Причем для этой функции не важно, ссылка на какой объект будет передаваться параметру. Главное, чтобы он реализовал методы трейта Printer. Сообственно
запись &impl Printer
и указывается, что это должна быть ссылка на объект, который реализует трейт Printer.
Затем в функции main создаются два объекта, ссылки на которые передаются в функцию display()
:
let tom = Person {name: String::from("Tom"), age: 36}; let hello = Message { title: String::from("Hello Rust")}; display(&tom); display(&hello);
Консольный вывод программы:
Person Tom; age: 36 Message: Hello Rust
Трейт как тип результата определяется следующим образом:
fn название_функции() -> impl название_трейта {
После оператора -> указывается ключевое слово impl, а затем идет название трейта. То есть, как бы говориться, что возвращаемый объект должен реализовать указанный трейт.
Например:
struct TextMessage { address: String, text: String} trait Sender{ fn send(&self); } impl Sender for TextMessage{ fn send(&self){ println!("Сообщение '{}' отправлено на адрес {}", self.text, self.address); } } fn create_message(addr: &str) -> impl Sender{ TextMessage {address: String::from(addr), text: String::from("Привет, ты спишь?") } } fn main(){ let text_message = create_message("sam@gmail.com"); text_message.send(); }
В данном случае структура TextMessage, которая представляет некоторое текстовое сообщение, реализует трейт Sender, в частности, его метод send()
.
Определенная здесь функция create_message()
в качестве результата возвращает объект трейта Sender:
fn create_message(addr: &str) -> impl Sender{ TextMessage {address: String::from(addr), text: String::from("Привет, ты спишь?") } }
Поскольку структура TextMessage реализует трейт Sender, то функция может возвратить объект этой структуры.
В функции main вызываем функцию create_message()
, получаем ее результат - объект структуры TextMessage и выполняем ее метод send()
:
let text_message = create_message("sam@gmail.com"); text_message.send();
Однако, эта возможность имеет существенные ограничения. Так, функция должна возвращать объкты только одного и того же типа. Например, в следующем случае мы получим ошибку на этапе компиляции:
struct VoiceMessage { address: String} struct TextMessage { address: String, text: String} trait Sender{ fn send(&self); } impl Sender for VoiceMessage{ fn send(&self){ println!("Голосовое сообщение отправлено на адрес {}", self.address); } } impl Sender for TextMessage{ fn send(&self){ println!("Сообщение '{}' отправлено на адрес {}", self.text, self.address); } } fn create_message(addr: &str, text_message: bool) -> impl Sender{ if text_message{ TextMessage {address: String::from(addr), text: String::from("Привет, ты спишь?") } } else { VoiceMessage {address: String::from(addr) } } } fn main(){ let text_message = create_message("sam@gmail.com", true); text_message.send(); let voice_message = create_message("bob@gmail.com", false); voice_message.send(); }
В данном случае добавлена структура VoiceMessage, которая тоже реализует трейт Sender. А функция create_message()
теперь в зависимости
от второго параметра возвращает либо объект TextMessage, либо объект VoiceMessage. И вроде бы все выглядит нормально, так как обе структуры
реализуют трейт Sender. Однако при компиляции компилятор радостно сообщит нам, что "if and else have incompatible types". То есть подвыражения в if и else возвращают объекты
разных типов, что не допускается.