Поиск…
Вступление
Черты - это способ описания «контракта», который должна реализовать struct
. Черты обычно определяют сигнатуры методов, но также могут обеспечивать реализации на основе других методов признака, обеспечивая при этом границы признаков .
Для тех, кто знаком с объектно-ориентированным программированием, черты можно рассматривать как интерфейсы с некоторыми незначительными отличиями.
Синтаксис
- trait Trait {fn method (...) -> ReturnType; ...}
- trait Trait: Bound {fn method (...) -> ReturnType; ...}
- impl Trait для типа {fn method (...) -> ReturnType {...} ...}
- impl <T> Trait for T где T: Bounds {fn method (...) -> ReturnType {...} ...}
замечания
- Черты обычно сравниваются с интерфейсами, но важно провести различие между ними. В языках OO, таких как Java, интерфейсы являются неотъемлемой частью классов, которые расширяют их. В Rust компилятор ничего не знает о чертах структуры, если эти черты не используются.
основы
Создание признака
trait Speak {
fn speak(&self) -> String;
}
Внедрение признака
struct Person;
struct Dog;
impl Speak for Person {
fn speak(&self) -> String {
String::from("Hello.")
}
}
impl Speak for Dog {
fn speak(&self) -> String {
String::from("Woof.")
}
}
fn main() {
let person = Person {};
let dog = Dog {};
println!("The person says {}", person.speak());
println!("The dog says {}", dog.speak());
}
Статическая и динамическая отправка
Можно создать функцию, которая принимает объекты, реализующие определенный признак.
Статическая отправка
fn generic_speak<T: Speak>(speaker: &T) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person);
generic_speak(&dog);
}
Здесь используется статическая диспетчеризация, что означает, что компилятор Rust создаст специализированные версии generic_speak
для типов Dog
и Person
. Это поколение специализированных версий полиморфной функции (или любого полиморфного объекта) во время компиляции называется Мономорфизацией .
Динамическая отправка
fn generic_speak(speaker: &Speak) {
println!("{0}", speaker.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
generic_speak(&person as &Speak);
generic_speak(&dog); // gets automatically coerced to &Speak
}
Здесь в скомпилированном двоичном generic_speak
существует только одна версия generic_speak
, а вызов generic_speak
speak()
выполняется с помощью поиска vtable во время выполнения. Таким образом, использование динамической диспетчеризации приводит к более быстрой компиляции и меньшему размеру скомпилированного двоичного файла, хотя во время выполнения он немного медленнее.
Объекты типа &Speak
или Box<Speak>
называются объектами признаков .
Связанные типы
- Использовать связанный тип, когда между типом, реализующим признак, и связанным с ним типом существует взаимно-однозначная взаимосвязь.
- Иногда он также известен как тип вывода , так как это элемент, присвоенный типу, когда мы применяем к нему черту.
Создание
trait GetItems {
type First;
// ^~~~ defines an associated type.
type Last: ?Sized;
// ^~~~~~~~ associated types may be constrained by traits as well
fn first_item(&self) -> &Self::First;
// ^~~~~~~~~~~ use `Self::` to refer to the associated type
fn last_item(&self) -> &Self::Last;
// ^~~~~~~~~~ associated types can be used as function output...
fn set_first_item(&mut self, item: Self::First);
// ^~~~~~~~~~~ ... input, and anywhere.
}
РЕАЛИЗАЦИЯ
impl<T, U: ?Sized> GetItems for (T, U) {
type First = T;
type Last = U;
// ^~~ assign the associated types
fn first_item(&self) -> &Self::First { &self.0 }
fn last_item(&self) -> &Self::Last { &self.1 }
fn set_first_item(&mut self, item: Self::First) { self.0 = item; }
}
impl<T> GetItems for [T; 3] {
type First = T;
type Last = T;
fn first_item(&self) -> &T { &self[0] }
// ^ you could refer to the actual type instead of `Self::First`
fn last_item(&self) -> &T { &self[2] }
fn set_first_item(&mut self, item: T) { self[0] = item; }
}
Ссылка на связанные типы
Если мы уверены, что тип T
реализует GetItems
например, в generics, мы могли бы просто использовать T::First
для получения ассоциированного типа.
fn get_first_and_last<T: GetItems>(obj: &T) -> (&T::First, &T::Last) {
// ^~~~~~~~ refer to an associated type
(obj.first_item(), obj.last_item())
}
В противном случае вам нужно явно указать компилятору, который реализует тип, реализующий
let array: [u32; 3] = [1, 2, 3];
let first: &<[u32; 3] as GetItems>::First = array.first_item();
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [u32; 3] may implement multiple traits which many
// of them provide the `First` associated type.
// thus the explicit "cast" is necessary here.
assert_eq!(*first, 1);
Ограничение со связанными типами
fn clone_first_and_last<T: GetItems>(obj: &T) -> (T::First, T::Last)
where T::First: Clone, T::Last: Clone
// ^~~~~ use the `where` clause to constraint associated types by traits
{
(obj.first_item().clone(), obj.last_item().clone())
}
fn get_first_u32<T: GetItems<First=u32>>(obj: &T) -> u32 {
// ^~~~~~~~~~~ constraint associated types by equality
*obj.first_item()
}
Методы по умолчанию
trait Speak {
fn speak(&self) -> String {
String::from("Hi.")
}
}
Метод будет вызван по умолчанию, за исключением случаев, если он был перезаписан в блоке impl
.
struct Human;
struct Cat;
impl Speak for Human {}
impl Speak for Cat {
fn speak(&self) -> String {
String::from("Meow.")
}
}
fn main() {
let human = Human {};
let cat = Cat {};
println!("The human says {}", human.speak());
println!("The cat says {}", cat.speak());
}
Выход :
Человек говорит, Привет.
Кошка говорит Мяу.
Помещение границы по признаку
При определении нового признака можно обеспечить, чтобы типы, желающие реализовать этот признак, проверяли ряд ограничений или ограничений.
Взяв пример из стандартной библиотеки, черта DerefMut
требует, чтобы тип сначала реализовал свою черту Deref
сестры:
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Это, в свою очередь, позволяет DerefMut
использовать связанный тип Target
определенный Deref
.
Хотя синтаксис может напоминать наследование:
- он включает все связанные элементы (константы, типы, функции, ...) связанного признака
- это позволяет полиморфизм от
&DerefMut
до&Deref
Это по-разному:
- можно использовать время жизни (например,
'static
) как связанное - невозможно переопределить элементы связанных объектов (даже не функции)
Поэтому лучше всего думать об этом как о отдельной концепции.
Типы нескольких связанных объектов
Также возможно добавить несколько типов объектов в функцию Static Dispatch .
fn mammal_speak<T: Person + Dog>(mammal: &T) {
println!("{0}", mammal.speak());
}
fn main() {
let person = Person {};
let dog = Dog {};
mammal_speak(&person);
mammal_speak(&dog);
}