Trait как параметр и результат функции

Последнее обновление: 21.04.2021

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

Trait как тип параметров

Формальная установка трейта в качестве типа данных для параметра:

название_параметра: &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

Trait как тип результата

Трейт как тип результата определяется следующим образом:

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 возвращают объекты разных типов, что не допускается.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850