Trait bound

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

Раннее в одной из предыдущих статей рассматривалось применение трейта в качестве параметра функции:

trait Printer{
    fn print(&self);
}

struct Person { name: String, age: u8}

impl Printer for Person{
 
    fn print(&self){
        println!("Person {}; age: {}", self.name, self.age);
    }
}

fn display(printable: &impl Printer) {
    printable.print();
}

fn main(){
    let tom = Person {name: String::from("Tom"), age: 36};
    display(&tom);
}

В частности, обратим внимание на функцию display(), которая в качестве параметра принимает трейт Printer:

fn display(printable: &impl Printer) {
    printable.print();
}

В действительности синтаксис impl Trait представляет упрощенный синтаксис того, что называется trait bound или ограничение трейта, которое устанавливает для параметров типа ограничения по применяемым типам. И в реальности эта функция разворачивается в следующую:

fn display<T: Printer>(printable: &T) {
    printable.print();
}

Выражение <T: Printer> говорит, что параметр T обязательно должен представлять тип, который реализует трейт Printer. То есть устанавливается ограничение на применяемые типы. Но в целом нет разницы, какой способ определения функции использовать.

Выражение where

Кроме выше предложенных двух вариантов функции Rust также предоставляет альтернативный синтаксис с использованием оператора where:

fn название_функции<T>(obj: &T) where T: трейт{

}

После оператора where указывается какой трейт представляет параметр.

Так функцию display() можно было бы переписать следующим образом:

fn display<T>(printable: &T) where T: Printer {
    printable.print();
}

Привязка нескольких трейтов

Выше в примере параметр функции представлял один трейт, однако мы можем определить параметр, который должен реализовать сразу несколько трейтов. Для установки ограничения типа на несколько трейтов применяется операция +:

fn название_функции(obj: &(impl трейт1 + трейт2 + .... + трейтN)) {

}

Рассмотрим на примере:

trait Printer{  fn print(&self); }
trait Sender{  fn send(&self); }

struct Message { text: String}

impl Printer for Message{
	fn print(&self){
        println!("Сообщение: {}", self.text);
    }
}
impl Sender for Message{
	fn send(&self){
        println!("Сообщение отправлено");
    }
}

fn process(obj: &(impl Printer + Sender)) {
    obj.print();
	obj.send();
}

fn main(){
    let mes = Message {text: String::from("Hello Rust")};
    process(&mes);
}

Консольный вывод:

Сообщение: Hello Rust
Сообщение отправлено

Здесь функция process принимает в качестве параметра объект, который реализует сразу два трейта - Printer и Sender:

fn process(obj: &(impl Printer + Sender)) {
    obj.print();
	obj.send();
}

Также мы могли использовать более длинную форму данной функции:

fn process<T: Printer + Sender>(obj: &T) {
    obj.print();
	obj.send();
}

Также можно использовать альтернативный синтаксис с оператором where:

fn process<T>(obj: &T) where T: Printer + Sender{
    obj.print();
	obj.send();
}

Применение нескольких параметров разных трейтов

Пример функции, которая применяет несколько параметров типа разных трейтов. Вариант с полным синтаксисом:

fn process<T: Printer + Editor, S: Printer + Sender>(obj1: &T, obj2: &S){

}

В данном случае функция process() типизированна двумя параметрами T и S, при этом тип T должен представлять одновременную реализацию трейтов Printer и Editor, а параметр S - реализацию трейтов Printer и Sender.

Вариант той же фукции с сокращенным синтаксисом:

fn process(obj1: &(impl Printer + Editor), obj2: &(impl Printer + Sender)){
}

Вариант с оператором where:

fn process<T, S>(obj1: &T, obj2: &S) where T: Printer + Editor, S: Printer + Sender{
}

Ограничения трейтов в обобщенных типах

Подобно тому как применяется ограничение трейтов к обобщенным функциям, она также может применяться при определении обобщенных типов. Например:

struct Person<T: Sender + Printer>{ device: T}

trait Printer{ fn print(&self, message: &str); }
trait Sender{ fn send(&self, message: &str); }

struct Smartphone {}

impl Printer for Smartphone{
	fn print(&self, message: &str){
        println!("{}", message);
    }
}
impl Sender for Smartphone{
	fn send(&self, message: &str){
        println!("Сообщение {} отправлено", message);
    }
}

fn main(){
	let iphone = Smartphone{};
    let tom = Person{device: iphone};
    tom.device.print("Hello Rust!");
    tom.device.send("Hello Rust!");
}

Здесь поле device структуры Person представляет тип, который реализуется сразу два трейта: Printer и Sender.

struct Person<T: Sender + Printer>{ device: T}

И в данном примере как раз таким типом является структура Smartphone.

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

struct Person<T> where T: Sender + Printer{ device: T}

Привязка трейтов в реализациях методов

Также можно использовать ограничения трейтов в реализациях методов:

struct Person<T>{ device: T}
impl<T: Sender + Printer> Person<T>{

	fn send_message(&self, message: &str){
		self.device.print(message);
		self.device.send(message);
	}
}

trait Printer{ fn print(&self, message: &str); }
trait Sender{ fn send(&self, message: &str); }

struct Smartphone {}

impl Printer for Smartphone{
	fn print(&self, message: &str){
        println!("{}", message);
    }
}
impl Sender for Smartphone{
	fn send(&self, message: &str){
        println!("Сообщение {} отправлено", message);
    }
}

fn main(){
	let iphone = Smartphone{};
    let tom = Person{device: iphone};
    tom.send_message("Hello Rust!");
}

Здесь определен метод send_message() для типа Person, для которого параметр T представляет тип, который одновременно реализует трейты Printer и Sender:

impl<T: Sender + Printer> Person<T>{

	fn send_message(&self, message: &str){
		self.device.print(message);
		self.device.send(message);
	}
}

В данном примере таким типом является структура Smartphone.

Также можно применять альтернативный синтаксис с оператором where:

impl<T> Person<T> where T: Sender + Printer{

	fn send_message(&self, message: &str){
		self.device.print(message);
		self.device.send(message);
	}
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850