Как и другие типы, трейты могут использоваться в качестве типа данных для параметров и возвращаемого результата функции.
Формальная установка трейта в качестве типа данных для параметра:
название_параметра: &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 возвращают объекты
разных типов, что не допускается.