Trait или типаж - это способ определения общего поведения для типов. Трейты позволяют абстрагироваться от конкретных типов и сосредоточиться на том, что эти типы умеют делать.
traitPrintable{// объявление трейта
fnprint(&self)-> String;// определяем, что Trait делает
}structPoint{x: i32,y: i32,}implPrintableforPoint{// применяем Trait для типа данных
fnprint(&self)-> String{format!("Point: ({}, {})",self.x,self.y)// пишем сам код
}}fnmain(){letp=Point{x: 3,y: 4};println!("{}",p.print());// Вывод: Point: (3, 4)
}
Инициализация типажа
При объявлении типажа можно оставить обязательную реализацию на потом, либо вписать реализацию функций в типаже по-умолчанию:
// типаж задаёт метод и ограничения по входным/выходным типам
traitLandVehicle{fnLandDrive(&self)-> String;}// типаж задаёт методы плюс их реализация по умолчанию
traitWaterVehicle{fnWaterDrive(&self){println!("Default float");}}
Применение типажей к структурам данных
Во время применения, если реализация по умолчанию была задана, то можно её переделать под конкретную структуру, либо использовать эту реализацию:
structSedan{}structRocketship{}// типаж LandVehicle не имеет реализации по умолчанию, реализуем тут
implLandVehicleforSedan{fnLandDrive(&self)-> String{format!("Car zoom-zoom!")}}// типаж WaterVehicle имеет выше реализацию по умолчанию, используем её
implWaterVehicleforRocketship{}
Наследование и объединение типажей
Типажи могут наследовать другие типажи с помощью синтаксиса trait NewTrait: OldTrait:
Любой тип, реализующий Displayable, обязан также реализовать Printable.
При объединении типажей, создаётся ярлык (alias). При этом каждый входящий в него типаж нужно отдельно применить к структуре данных. При этом можно также использовать реализацию определённых в типаже методов по умолчанию, либо написать свою.
// создание ярлыка
traitAmphibiousVehicle: LandVehicle+WaterVehicle{}// применение типажей к структуре
implAmphibiousVehicleforCarrier{}implLandVehicleforCarrier{fnLandDrive(&self)-> String{format!("Use air thrust to travel on land")}}implWaterVehicleforCarrier{}
Вызов методов экземпляра структуры определённого типажа
Типажи определяют, какие возможности есть у generic типа T:
fnmy_function<T>(value: T)-> TwhereT: SomeTrait+AnotherTrait{/* function body */}
Eq (Equality)
Проверяет равенство (==) или неравенство (!=);
Включает PartialEq (проверяет только равенство) + гарантирует тождество;
// Без Eq типажа:
// ❌ не будет компилироваться ==
fnfind_value<T>(items: &[T],target: T)-> bool{items.iter().any(|x|x==&target)// Error
}// Добавим Eq trait:
fnfind_value<T>(items: &[T],target: T)-> boolwhereT: Eq// теперь можно использовать ==
{items.iter().any(|x|x==&target)// ✅ работает!
}
std::hash::Hash
Позволяет конвертировать значения в хэш;
Требуется для хранения в HashMap, HashSet, или других коллекций с хэшом;
Тип с Hash может быть ключом или значением словаря:
usestd::collections::HashSet;// Без Hash:
// ❌ не будет компилироваться - HashSet требует Hash
fncreate_set<T>(items: Vec<T>)-> HashSet<T>{items.into_iter().collect()// Error: T doesn't implement Hash
}// С Hash:
fncreate_set<T>(items: Vec<T>)-> HashSet<T>whereT: Eq+std::hash::Hash// требуется для HashSet
{items.into_iter().collect()// ✅ работает!
}
Clone
Позволяет дублировать значение;
Разрешает метод .clone();
Нужно для копирования значений без взятия владения.
// Без Clone:
fnduplicate_first<T>(items: &[T])-> Option<T>{items.first().map(|x|x)// Error: can't return T from &T
}// Включаем Clone:
fnduplicate_first<T>(items: &[T])-> Option<T>whereT: Clone// можно клонировать
{items.first().cloned()// ✅ работает! - создаёт копию
}