Трейты
Syntax
Trait →
unsafe? trait IDENTIFIER GenericParams? ( : TypeParamBounds? )? WhereClause?
{
InnerAttribute*
AssociatedItem*
}
Трейт описывает абстрактный интерфейс, который могут реализовывать типы. Этот интерфейс состоит из ассоциированных элементов, которые бывают трёх видов:
Объявление трейта определяет трейт в пространстве имён типов модуля или блока, где он находится.
Ассоциированные элементы определяются как члены трейта в соответствующих пространствах имён. Ассоциированные типы определяются в пространстве имён типов. Ассоциированные константы и ассоциированные функции определяются в пространстве имён значений.
Все трейты определяют неявный параметр типа Self, который ссылается на “тип,
который реализует этот интерфейс”. Трейты также могут содержать дополнительные
параметры типа. Эти параметры типа, включая Self, могут быть ограничены
другими трейтами и так далее generics.
Трейты реализуются для конкретных типов через отдельные реализации.
Функции трейта могут опускать тело функции, заменяя его точкой с запятой. Это указывает, что реализация должна определить функцию. Если функция трейта определяет тело, это определение действует как значение по умолчанию для любой реализации, которая его не переопределяет. Аналогично, ассоциированные константы могут опускать знак равенства и выражение, чтобы указать, что реализации должны определить значение константы. Ассоциированные типы никогда не должны определять тип, тип может быть указан только в реализации.
#![allow(unused)] fn main() { // Примеры ассоциированных элементов трейта с определениями и без. trait Example { const CONST_NO_DEFAULT: i32; const CONST_WITH_DEFAULT: i32 = 99; type TypeNoDefault; fn method_without_default(&self); fn method_with_default(&self) {} } }
Функции трейта не могут быть const.
Ограничения трейтов
Универсальные элементы могут использовать трейты как ограничения на свои параметры типа.
Универсальные трейты
Параметры типа могут быть указаны для трейта, чтобы сделать его универсальным. Они появляются после имени трейта, используя тот же синтаксис, что и в универсальных функциях.
#![allow(unused)] fn main() { trait Seq<T> { fn len(&self) -> u32; fn elt_at(&self, n: u32) -> T; fn iter<F>(&self, f: F) where F: Fn(T); } }
Совместимость с dyn
Совместимый с dyn трейт может быть базовым трейтом для объекта трейта. Трейт совместим с dyn, если он обладает следующими качествами:
- Все супертрейты также должны быть совместимы с dyn.
Sizedне должен быть супертрейтом. Другими словами, он не должен требоватьSelf: Sized.
- Он не должен иметь ассоциированных констант.
- Он не должен иметь ассоциированных типов с обобщёнными параметрами.
- Все ассоциированные функции должны либо быть диспетчеризуемыми из объекта трейта, либо быть явно недиспетчеризуемыми:
- Диспетчеризуемые функции должны:
- Не иметь параметров типа (хотя параметры времени жизни разрешены).
- Быть методом, который не использует
Selfкроме как в типе приёмника. - Иметь приёмник одного из следующих типов:
- Не иметь непрозрачный тип возврата; то есть,
- Не быть
async fn(который имеет скрытый типFuture). - Не иметь тип
impl Traitв позиции возврата (fn example(&self) -> impl Trait).
- Не быть
- Не иметь ограничения
where Self: Sized(тип приёмникаSelf(т.е.self) подразумевает это).
- Явно недиспетчеризуемые функции требуют:
- Иметь ограничение
where Self: Sized(тип приёмникаSelf(т.е.self) подразумевает это).
- Иметь ограничение
- Диспетчеризуемые функции должны:
- Трейты
AsyncFn,AsyncFnMutиAsyncFnOnceне совместимы с dyn.
Note
Ранее эта концепция была известна как объектная безопасность.
#![allow(unused)] fn main() { use std::rc::Rc; use std::sync::Arc; use std::pin::Pin; // Примеры методов, совместимых с dyn. trait TraitMethods { fn by_ref(self: &Self) {} fn by_ref_mut(self: &mut Self) {} fn by_box(self: Box<Self>) {} fn by_rc(self: Rc<Self>) {} fn by_arc(self: Arc<Self>) {} fn by_pin(self: Pin<&Self>) {} fn with_lifetime<'a>(self: &'a Self) {} fn nested_pin(self: Pin<Arc<Self>>) {} } struct S; impl TraitMethods for S {} let t: Box<dyn TraitMethods> = Box::new(S); }
#![allow(unused)] fn main() { // Этот трейт совместим с dyn, но эти методы не могут быть диспетчеризованы через объект трейта. trait NonDispatchable { // Не-методы не могут быть диспетчеризованы. fn foo() where Self: Sized {} // Тип Self неизвестен до времени выполнения. fn returns(&self) -> Self where Self: Sized; // `other` может быть другим конкретным типом приёмника. fn param(&self, other: Self) where Self: Sized {} // Обобщённые параметры не совместимы с vtables. fn typed<T>(&self, x: T) where Self: Sized {} } struct S; impl NonDispatchable for S { fn returns(&self) -> Self where Self: Sized { S } } let obj: Box<dyn NonDispatchable> = Box::new(S); obj.returns(); // ОШИБКА: нельзя вызвать с возвратом Self obj.param(S); // ОШИБКА: нельзя вызвать с параметром Self obj.typed(1); // ОШИБКА: нельзя вызвать с обобщённым типом }
#![allow(unused)] fn main() { use std::rc::Rc; // Примеры трейтов, несовместимых с dyn. trait DynIncompatible { const CONST: i32 = 1; // ОШИБКА: не может иметь ассоциированную константу fn foo() {} // ОШИБКА: ассоциированная функция без Sized fn returns(&self) -> Self; // ОШИБКА: Self в типе возврата fn typed<T>(&self, x: T) {} // ОШИБКА: имеет параметры обобщённого типа fn nested(self: Rc<Box<Self>>) {} // ОШИБКА: вложенный приёмник не может быть понижен } struct S; impl DynIncompatible for S { fn returns(&self) -> Self { S } } let obj: Box<dyn DynIncompatible> = Box::new(S); // ОШИБКА }
#![allow(unused)] fn main() { // Трейты с `Self: Sized` несовместимы с dyn. trait TraitWithSize where Self: Sized {} struct S; impl TraitWithSize for S {} let obj: Box<dyn TraitWithSize> = Box::new(S); // ОШИБКА }
#![allow(unused)] fn main() { // Несовместим с dyn, если `Self` является аргументом типа. trait Super<A> {} trait WithSelf: Super<Self> where Self: Sized {} struct S; impl<A> Super<A> for S {} impl WithSelf for S {} let obj: Box<dyn WithSelf> = Box::new(S); // ОШИБКА: нельзя использовать параметр типа `Self` }
Супертрейты
Супертрейты - это трейты, которые должны быть реализованы для типа, чтобы реализовать конкретный трейт. Более того, везде, где generics или объект трейта ограничен трейтом, он имеет доступ к ассоциированным элементам своих супертрейтов.
Супертрейты объявляются через ограничения трейтов на тип Self трейта и
транзитивно через супертрейты трейтов, объявленных в этих ограничениях трейтов.
Ошибкой является случай, когда трейт является своим собственным супертрейтом.
Трейт с супертрейтом называется подтрейтом своего супертрейта.
Следующий пример объявляет Shape супертрейтом Circle.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle: Shape { fn radius(&self) -> f64; } }
А следующий пример - тот же самый, но с использованием where-предложений.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64; } }
Следующий пример даёт radius реализацию по умолчанию, используя функцию area
из Shape.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64 { // A = pi * r^2 // так что алгебраически, // r = sqrt(A / pi) (self.area() / std::f64::consts::PI).sqrt() } } }
Следующий пример вызывает метод супертрейта на универсальном параметре.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle: Shape { fn radius(&self) -> f64; } fn print_area_and_radius<C: Circle>(c: C) { // Здесь мы вызываем метод area из супертрейта `Shape` трейта `Circle`. println!("Area: {}", c.area()); println!("Radius: {}", c.radius()); } }
Аналогично, вот пример вызова методов супертрейтов на объектах трейтов.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle: Shape { fn radius(&self) -> f64; } struct UnitCircle; impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } } impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } } let circle = UnitCircle; let circle = Box::new(circle) as Box<dyn Circle>; let nonsense = circle.radius() * circle.area(); }
Небезопасные трейты
Элементы трейта, которые начинаются с ключевого слова unsafe, указывают, что реализация
трейта может быть небезопасной. Безопасно использовать корректно реализованный небезопасный трейт.
Реализация трейта также должна начинаться с ключевого слова unsafe.
Sync и Send являются примерами небезопасных трейтов.
Паттерны параметров
Параметры в ассоциированных функциях без тела допускают только паттерны IDENTIFIER или _ универсальный шаблон, а также форму, разрешённую SelfParam. mut IDENTIFIER в настоящее время разрешён, но он устарел и станет жёсткой ошибкой в будущем.
#![allow(unused)] fn main() { trait T { fn f1(&self); fn f2(x: Self, _: i32); } }
#![allow(unused)] fn main() { trait T { fn f2(&x: &i32); // ОШИБКА: паттерны не разрешены в функциях без тел } }
Параметры в ассоциированных функциях с телом допускают только неопровержимые паттерны.
#![allow(unused)] fn main() { trait T { fn f1((a, b): (i32, i32)) {} // OK: паттерн неопровержим } }
#![allow(unused)] fn main() { trait T { fn f1(123: i32) {} // ОШИБКА: паттерн опровержим fn f2(Some(x): Option<i32>) {} // ОШИБКА: паттерн опровержим } }
2018 Edition differences
До редакции 2018 года паттерн для параметра ассоциированной функции был опциональным:
#![allow(unused)] fn main() { // Редакция 2015 trait T { fn f(i32); // OK: идентификаторы параметров не требуются } }Начиная с редакции 2018 года, паттерны больше не являются опциональными.
2018 Edition differences
До редакции 2018 года параметры в ассоциированных функциях с телом ограничивались следующими видами паттернов:
- IDENTIFIER
mutIDENTIFIER_&IDENTIFIER&&IDENTIFIER#![allow(unused)] fn main() { // Редакция 2015 trait T { fn f1((a, b): (i32, i32)) {} // ОШИБКА: паттерн не разрешён } }Начиная с 2018 года, все неопровержимые паттерны разрешены, как описано в items.traits.params.patterns-with-body.
Видимость элементов
Элементы трейта синтаксически допускают аннотацию Видимости, но это
отклоняется при проверке трейта. Это позволяет разбирать элементы с
унифицированным синтаксисом в различных контекстах, где они используются. Например,
пустой фрагмент спецификатора vis макроса может использоваться для элементов трейта, где
правило макроса может использоваться в других ситуациях, где видимость разрешена.
macro_rules! create_method { ($vis:vis $name:ident) => { $vis fn $name(&self) {} }; } trait T1 { // Пустой `vis` разрешён. create_method! { method_of_t1 } } struct S; impl S { // Видимость разрешена здесь. create_method! { pub method_of_s } } impl T1 for S {} fn main() { let s = S; s.method_of_t1(); s.method_of_s(); }