Trait

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

Trait позволяет определить некоторое поведение или набор методов без реализации. А реализацию методов - конкретные действия, выполняемые методом, предоставляют другие типы (структуры и перечисления), которые применяют данный трейт. В других языках программирования есть в некоторой степени похожая функциональность - интерфейсы.

Формальное определение трейта с одним методом:

trait название_трейта{     // определяем трейт
	fn метод_трейта(&self);     // определение метода трейта
}

После ключевого слова trait идет название трейта (обычно начинается с большой буквы). Затем идет блок трейта, который содержит набор определений методов без реализации.

Первый и обязательный параметр метода (&self) представляет ссылку на текущий объект. Но как и другие функции может содержать и другие параметры. Единственное он не имеет реализации.

Затем структуры и перечисления могут применить трейт и реализовать его методы. Формальная реализация трейта:

impl название_трейта for структура/перечисление{	// реализация трейта
	
	fn метод_трейта(&self){ 	// реализация метода трейта
		// действия метода
	}
}

После ключевого слова impl указывается имя реализуемого трейта. Далее идет оператор for, после которого указывается для какой структуры или перечисления реализуется трейт.

Далее в блоке кода содержится реализация для методов трейта.

Например, определим простейший трейт, который будет применяться для вывода информации об объекте:

trait Printer{
	fn print(&self);
}

Данный трейт называется Printer. Он содержит только один метод - print(). Причем этот метод не содержит тела - конкретной реализации.

Допустим, у нас есть следующая структура Person:

struct Person { name: String, age: u8}

Теперь реализуем трейт Printer для структуры Person:

impl Printer for Person{

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

Реализация метода print для структуры Person просто выводит значения его полей name и age на консоль.

Теперь применим эту реализацию:

fn main(){
	
	let tom = Person{ name: String::from("Tom"), age: 36 };
	tom.print();	// Person Tom; age: 36
}
struct Person { name: String, age: u8}

trait Printer{
	fn print(&self);
}

impl Printer for Person{

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

Поскольку мы реализовали трейт Printer для структуры Person, то мы можем для объекта структуры Person вызывать метод print()

tom.print();	// Person Tom; age: 36

При этом, как и в случае с методами структур, значение для параметра &self не указывается.

Методы трейтов также могут принимать другие параметры и возвращать некоторый результат. Например, определим в трейте Printer метод, который будет принимать числовой параметр. Этот параметр будет указывать, сколько раз надо вывести информацию:

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

struct Person { name: String, age: u8}

trait Printer{
	fn print_multiple_copies(&self, times: u8);
}

impl Printer for Person{

	fn print_multiple_copies(&self, mut number: u8){
		while number > 0{
			println!("Person {}; age: {}", self.name, self.age);
			number-=1;
		}
	}
}

В данном случае метод для вывода информации называется print_multiple_copies().

Для вывода информации несколько раз применяется цикл while, который срабатывает пока параметр number не станет равным 0.

И поскольку теперь метод принимает больше одного параметра, для второго параметра нам надо передать некоторое числовое значение:

tom.print_multiple_copies(3);

И в данном случае метод выведет информацию об объекте три раза.

И пример возвращения результата. Добавим метод, который будет выполнять условный предпросмотр - возвращать выводимую информацию:

fn main(){
	
	let tom = Person{ name: String::from("Tom"), age: 36 };
	let tom_preview = tom.preview();
	println!("{}", tom_preview);
}

struct Person { name: String, age: u8}

trait Printer{
	fn preview(&self) -> String;
}

impl Printer for Person{

	fn preview(&self) -> String{
		format!("[Предпросмотр] Person {}; age: {}", self.name, self.age)
	}
}

Здесь в трейте Printer определен метод preview(), который должен возвращать объект типа String.

В реализации метода preview() для структуры Person применяется макрос format!, который похож на макрос println!. Он тоже принимает набор значений и позволяет вложить их в строку. Но при этом format! возвращает значение типа String, которое содержит полученную строку.

В примерах выше трейт содержал по одному методу. Однако если трейт содержит несколько методов, то при реализации трейта для структуры или перечисления необходимо реализовать все методы трейта.

Реализация по умолчанию

Трейт может содержать реализацию по умолчанию. В этом случае структура может не реализовать даный метод и воспользоваться реализацией по умолчанию:

fn main(){
	
	let tom = Person{ name: String::from("Tom"), age: 36 };
	tom.print();
	
	let hello = Message{ text: String::from("Hello Rust!")};
	hello.print();
}

trait Printer{
	fn print(&self){
		println!("Вывод данных на консоль...");
	}
}

struct Person { name: String, age: u8}
struct Message { text: String}

impl Printer for Person{
	// переопределяем реализацию метода
	fn print(&self){
		println!("Person {}; age: {}", self.name, self.age);
	}
}
impl Printer for Message{
	// используем реализацию метода по умолчанию
}

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

Person Tom; age: 36
Вывод данных на консоль...

Здесь трейт Printer определяет простейшую реализацию по умолчанию для метода print():

trait Printer{
	fn print(&self){
		println!("Вывод данных на консоль...");
	}
}

Далее данный трейт применяют две структуры - Person и Message. Но если для структуры Person определена своя реализация метода print(), то для структуры Message просто берется реализация по умолчанию:

impl Printer for Message{
	// используем реализацию метода по умолчанию
}

Однако если бы трейт Printer не содержал бы реализацию по умолчанию для метода print(), тогда ее обязательно надо было бы определять для всех применяющих даный трейт структур.

Обращение к методам трейта внутри трейта

Одни методы трейта могут обращаться к другим методам трейта:

fn main(){

	let tom = Person{ name: String::from("Tom"), age: 36 };
	tom.print();
}

struct Person { name: String, age: u8}

trait Printer{
	fn preview(&self) -> String;
	fn print(&self){
		println!("{}", self.preview());
	}
}

impl Printer for Person{

	fn preview(&self) -> String{
		format!("Person {}; age: {}", self.name, self.age)
	}
}

Здесь трейт Printer содержит два метода. Первый метод - preview() не имеет реалиации:

fn preview(&self) -> String;

Мы не знаем, как для конкретных структур будет релизован этот метод. Мы только знаем, что он возвращает объект String - некоторую строку, которую мы можем использовать.

Второй метод - print() имеет реализацию по умолчанию:

fn print(&self){
	println!("{}", self.preview());
}

Здесь мы используем первый метод трейта, обращаясь к нему через self.preview(). self внутри трейта указывает на текущий объект трейта, поэтому через self мы можем обращаться к любым методам трейта внутри этого трейта. Таким образом, на консоль будет выводится результат метода preview(), реализация которого будет определяться для каждой структуры отдельно.

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