Traits

Инициализация типажа

Типаж нужен для организации интерфейса: он задаёт ограничения-особенности поведения для переменных или структур с неопределёнными (generic) переменными. Мы отделяем объявление типажа от его реализации. При объявлении типажа можно оставить обязательную реализацию на потом, либо вписать реализацию функций в типаже по-умолчанию:

// типаж задаёт метод и ограничения по входным/выходным типам
trait LandVehicle {  
    fn LandDrive(&self) -> String; }  

// типаж задаёт методы плюс их реализация по умолчанию
trait WaterVehicle {  
    fn WaterDrive(&self) { println!("Default float"); }  
}

Применение типажей к структурам данных

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

struct Sedan {}  
struct Rocketship {}  

// типаж LandVehicle не имеет реализации по умолчанию, реализуем тут
impl LandVehicle for Sedan {  
    fn LandDrive(&self) -> String { format!("Car zoom-zoom!") } }

// типаж WaterVehicle имеет выше реализацию по умолчанию, используем её
impl WaterVehicle for Rocketship {}

Объединение типажей

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

// создание ярлыка
trait AmphibiousVehicle: LandVehicle + WaterVehicle {}

// применение типажей к структуре
impl AmphibiousVehicle for Carrier {}  
impl LandVehicle for Carrier {  
    fn LandDrive(&self) -> String { format!("Use air thrust to travel on land") }  
}  
impl WaterVehicle for Carrier {}

Вызов методов экземпляра структуры определённого типажа

fn main() {  
    let toyota_camry = Sedan {};  
    println!("{}",toyota_camry.LandDrive());
  
    let rs = Rocketship {};  
    rs.WaterDrive();  
  
    let project_x = Carrier {};  
    println!("{}",project_x.LandDrive());  
    project_x.WaterDrive();  
}

Встроенные типажи для generic данных у функций

Типажи определяют, какие возможности есть у generic типа T:

fn my_function<T>(value: T) -> T 
where 
    T: SomeTrait + AnotherTrait 
{ /* function body */ } 

Eq (Equality)

  • Проверяет равенство (==) или неравенство (!=);
  • Включает PartialEq (проверяет только равенство) + гарантирует тождество;
// Без Eq типажа:
// ❌ не будет компилироваться ==
fn find_value<T>(items: &[T], target: T) -> bool {
    items.iter().any(|x| x == &target) // Error
}

// Добавим Eq trait:
fn find_value<T>(items: &[T], target: T) -> bool 
where 
    T: Eq  // теперь можно использовать ==
{
    items.iter().any(|x| x == &target) // ✅ работает!
}

std::hash::Hash

  • Позволяет конвертировать значения в хэш;
  • Требуется для хранения в HashMapHashSet, или других коллекций с хэшом;
  • Тип с Hash может быть ключом или значением словаря:
use std::collections::HashSet;
// Без Hash:
// ❌ не будет компилироваться - HashSet требует Hash
fn create_set<T>(items: Vec<T>) -> HashSet<T> {
    items.into_iter().collect() // Error: T doesn't implement Hash
}

// С Hash:
fn create_set<T>(items: Vec<T>) -> HashSet<T> 
where 
    T: Eq + std::hash::Hash  // требуется для HashSet
{
    items.into_iter().collect() // ✅ работает!
}

Clone

  • Позволяет дублировать значение;
  • Разрешает метод .clone();
  • Нужно для копирования значений без взятия владения.
// Без Clone:
fn duplicate_first<T>(items: &[T]) -> Option<T> {
    items.first().map(|x| x) // Error: can't return T from &T
}

// Включаем Clone:
fn duplicate_first<T>(items: &[T]) -> Option<T> 
where 
    T: Clone  // можно клонировать
{
    items.first().cloned() // ✅ работает! - создаёт копию
}

Типовые комбинации Trait:

Назначение Типовые Traits Пример
Hash collections Eq + Hash HashMap<K, V>HashSet<T>
Sorting/ordering Ord or PartialOrd Sorting vectors, BTreeMap
Display/printing Debug or Display println!format!
Copying values Clone or Copy Duplicating data