Ассоциированные элементы
Syntax
AssociatedItem →
OuterAttribute* (
MacroInvocationSemi
| ( Visibility? ( TypeAlias | ConstantItem | Function ) )
)
Ассоциированные элементы - это элементы, объявленные в трейтах или определённые в реализациях. Они называются так, потому что определяются на ассоциированном типе — типе в реализации.
Они являются подмножеством видов элементов, которые можно объявить в модуле. Конкретно, существуют ассоциированные функции (включая методы), ассоциированные типы и ассоциированные константы.
Ассоциированные элементы полезны, когда ассоциированный элемент логически связан с
ассоциирующим элементом. Например, метод is_some на Option внутренне
связан с Option, поэтому должен быть ассоциированным.
Каждый вид ассоциированного элемента бывает двух разновидностей: определения, которые содержат фактическую реализацию, и объявления, которые объявляют сигнатуры для определений.
Именно объявления составляют контракт трейтов и то, что доступно на обобщённых типах.
Ассоциированные функции и методы
Ассоциированные функции - это функции, ассоциированные с типом.
Объявление ассоциированной функции объявляет сигнатуру для определения
ассоциированной функции. Оно записывается как элемент функции, за исключением того, что
тело функции заменяется на ;.
Идентификатор - это имя функции.
Обобщения, список параметров, тип возвращаемого значения и where-предложение ассоциированной функции должны быть такими же, как в объявлении ассоциированной функции.
Определение ассоциированной функции определяет функцию, ассоциированную с другим типом. Оно записывается так же, как элемент функции.
Note
Распространённый пример - ассоциированная функция с именем
new, которая возвращает значение типа, с которым она ассоциирована.
struct Struct { field: i32 } impl Struct { fn new() -> Struct { Struct { field: 0i32 } } } fn main () { let _struct = Struct::new(); }
Когда ассоциированная функция объявлена в трейте, функция также может быть
вызвана с путём, который является путём к трейту, дополненным именем
трейта. Когда это происходит, она заменяется на <_ as Trait>::function_name.
#![allow(unused)] fn main() { trait Num { fn from_i32(n: i32) -> Self; } impl Num for f64 { fn from_i32(n: i32) -> f64 { n as f64 } } // Эти 4 варианта эквивалентны в данном случае. let _: f64 = Num::from_i32(42); let _: f64 = <_ as Num>::from_i32(42); let _: f64 = <f64 as Num>::from_i32(42); let _: f64 = f64::from_i32(42); }
Методы
Ассоциированные функции, первый параметр которых назван self, называются методами
и могут быть вызваны с использованием оператора вызова метода, например, x.foo(), а
также обычной нотации вызова функции.
Если тип параметра self указан, он ограничен типами, разрешающимися
в один из сгенерированных следующей грамматикой (где 'lt обозначает некоторое произвольное
время жизни):
P = &'lt S | &'lt mut S | Box<S> | Rc<S> | Arc<S> | Pin<P>
S = Self | P
Терминал Self в этой грамматике обозначает тип, разрешающийся в реализующий тип.
Это также может включать контекстный псевдоним типа Self, другие псевдонимы типов
или проекции ассоциированных типов, разрешающиеся в реализующий тип.
#![allow(unused)] fn main() { use std::rc::Rc; use std::sync::Arc; use std::pin::Pin; // Примеры методов, реализованных на структуре `Example`. struct Example; type Alias = Example; trait Trait { type Output; } impl Trait for Example { type Output = Example; } impl Example { fn by_value(self: Self) {} 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 explicit_type(self: Arc<Example>) {} fn with_lifetime<'a>(self: &'a Self) {} fn nested<'a>(self: &mut &'a Arc<Rc<Box<Alias>>>) {} fn via_projection(self: <Example as Trait>::Output) {} } }
Может использоваться сокращённый синтаксис без указания типа, который имеет следующие эквиваленты:
| Сокращение | Эквивалент |
|---|---|
self | self: Self |
&'lifetime self | self: &'lifetime Self |
&'lifetime mut self | self: &'lifetime mut Self |
Note
Времена жизни могут быть, и обычно опускаются с этим сокращением.
Если параметр self предварен mut, он становится изменяемой переменной,
аналогично обычным параметрам, использующим mut идентификаторный образец. Например:
#![allow(unused)] fn main() { trait Changer: Sized { fn change(mut self) {} fn modify(mut self: Box<Self>) {} } }
В качестве примера методов в трейте рассмотрим следующее:
#![allow(unused)] fn main() { type Surface = i32; type BoundingBox = i32; trait Shape { fn draw(&self, surface: Surface); fn bounding_box(&self) -> BoundingBox; } }
Это определяет трейт с двумя методами. Все значения, которые имеют реализации
этого трейта, пока трейт находится в области видимости, могут иметь свои методы draw и
bounding_box вызванными.
#![allow(unused)] fn main() { type Surface = i32; type BoundingBox = i32; trait Shape { fn draw(&self, surface: Surface); fn bounding_box(&self) -> BoundingBox; } struct Circle { // ... } impl Shape for Circle { // ... fn draw(&self, _: Surface) {} fn bounding_box(&self) -> BoundingBox { 0i32 } } impl Circle { fn new() -> Circle { Circle{} } } let circle_shape = Circle::new(); let bounding_box = circle_shape.bounding_box(); }
2018 Edition differences
В редакции 2015 можно было объявлять методы трейта с анонимными параметрами (например,
fn foo(u8)). Это устарело и является ошибкой начиная с редакции 2018. Все параметры должны иметь имя аргумента.
Атрибуты на параметрах методов
Атрибуты на параметрах методов следуют тем же правилам и ограничениям, что и обычные параметры функций.
Ассоциированные типы
Ассоциированные типы - это псевдонимы типов, ассоциированные с другим типом.
Ассоциированные типы не могут быть определены в собственных реализациях и не могут иметь реализацию по умолчанию в трейтах.
Объявление ассоциированного типа объявляет сигнатуру для определений ассоциированного типа.
Оно записывается в одной из следующих форм, где Assoc - это
имя ассоциированного типа, Params - это разделённый запятыми список параметров типа,
времени жизни или констант, Bounds - это разделённый плюсами список ограничений трейтов,
которым должен удовлетворять ассоциированный тип, и WhereBounds - это разделённый запятыми список
ограничений, которым должны удовлетворять параметры:
type Assoc;
type Assoc: Bounds;
type Assoc<Params>;
type Assoc<Params>: Bounds;
type Assoc<Params> where WhereBounds;
type Assoc<Params>: Bounds where WhereBounds;
Идентификатор - это имя объявленного псевдонима типа.
Опциональные ограничения трейтов должны выполняться реализациями псевдонима типа.
Существует неявное ограничение Sized на ассоциированные типы, которое может быть ослаблено с использованием специального ограничения ?Sized.
Определение ассоциированного типа определяет псевдоним типа для реализации трейта на типе.
Они записываются аналогично объявлению ассоциированного типа, но не могут содержать Bounds, но вместо этого должны содержать Type:
type Assoc = Type;
type Assoc<Params> = Type; // тип `Type` здесь может ссылаться на `Params`
type Assoc<Params> = Type where WhereBounds;
type Assoc<Params> where WhereBounds = Type; // устарело, предпочтительнее форма выше
Если тип Item имеет ассоциированный тип Assoc из трейта Trait, то
<Item as Trait>::Assoc - это тип, который является псевдонимом типа, указанного в
определении ассоциированного типа.
Более того, если Item является параметром типа, то Item::Assoc может использоваться в параметрах типа.
Ассоциированные типы могут включать обобщённые параметры и where-предложения; они
часто называются обобщёнными ассоциированными типами, или GATs. Если тип Thing
имеет ассоциированный тип Item из трейта Trait с обобщениями <'a>, то
тип может быть назван как <Thing as Trait>::Item<'x>, где 'x - некоторое время жизни
в области видимости. В этом случае 'x будет использоваться везде, где 'a появляется в определениях
ассоциированных типов в impls.
trait AssociatedType { // Объявление ассоциированного типа type Assoc; } struct Struct; struct OtherStruct; impl AssociatedType for Struct { // Определение ассоциированного типа type Assoc = OtherStruct; } impl OtherStruct { fn new() -> OtherStruct { OtherStruct } } fn main() { // Использование ассоциированного типа для ссылки на OtherStruct как <Struct as AssociatedType>::Assoc let _other_struct: OtherStruct = <Struct as AssociatedType>::Assoc::new(); }
Пример ассоциированных типов с обобщениями и where-предложениями:
struct ArrayLender<'a, T>(&'a mut [T; 16]); trait Lend { // Объявление обобщённого ассоциированного типа type Lender<'a> where Self: 'a; fn lend<'a>(&'a mut self) -> Self::Lender<'a>; } impl<T> Lend for [T; 16] { // Определение обобщённого ассоциированного типа type Lender<'a> = ArrayLender<'a, T> where Self: 'a; fn lend<'a>(&'a mut self) -> Self::Lender<'a> { ArrayLender(self) } } fn borrow<'a, T: Lend>(array: &'a mut T) -> <T as Lend>::Lender<'a> { array.lend() } fn main() { let mut array = [0usize; 16]; let lender = borrow(&mut array); }
Пример контейнера с ассоциированными типами
Рассмотрим следующий пример трейта Container. Заметьте, что тип
доступен для использования в сигнатурах методов:
#![allow(unused)] fn main() { trait Container { type E; fn empty() -> Self; fn insert(&mut self, elem: Self::E); } }
Для того чтобы тип реализовал этот трейт, он должен не только предоставить
реализации для каждого метода, но он должен указать тип E. Вот
реализация Container для типа стандартной библиотеки Vec:
#![allow(unused)] fn main() { trait Container { type E; fn empty() -> Self; fn insert(&mut self, elem: Self::E); } impl<T> Container for Vec<T> { type E = T; fn empty() -> Vec<T> { Vec::new() } fn insert(&mut self, x: T) { self.push(x); } } }
Связь между Bounds и WhereBounds
В этом примере:
#![allow(unused)] fn main() { use std::fmt::Debug; trait Example { type Output<T>: Ord where T: Debug; } }
Для ссылки на ассоциированный тип типа <X as Example>::Output<Y>, сам ассоциированный тип должен быть Ord, и тип Y должен быть Debug.
Обязательные where-предложения на обобщённых ассоциированных типах
Объявления обобщённых ассоциированных типов в трейтах в настоящее время могут требовать список where-предложений, зависящих от функций в трейте и того, как используется GAT. Эти правила могут быть ослаблены в будущем; обновления можно найти в репозитории инициативы обобщённых ассоциированных типов.
Короче говоря, эти where-предложения требуются для максимизации разрешённых определений ассоциированного типа в impls. Для этого любые предложения, которые могут быть доказаны, что выполняются на функциях (используя параметры функции или трейта) где GAT появляется как вход или выход, также должны быть записаны на самом GAT.
#![allow(unused)] fn main() { trait LendingIterator { type Item<'x> where Self: 'x; fn next<'a>(&'a mut self) -> Self::Item<'a>; } }
В вышеприведённом, на функции next, мы можем доказать, что Self: 'a, из-за
подразумеваемых ограничений от &'a mut self; поэтому мы должны записать эквивалентное
ограничение на самом GAT: where Self: 'x.
Когда есть несколько функций в трейте, которые используют GAT, то используется пересечение ограничений из разных функций, а не объединение.
#![allow(unused)] fn main() { trait Check<T> { type Checker<'x>; fn create_checker<'a>(item: &'a T) -> Self::Checker<'a>; fn do_check(checker: Self::Checker<'_>); } }
В этом примере никакие ограничения не требуются на type Checker<'a>;. Хотя мы
знаем, что T: 'a на create_checker, мы не знаем этого на do_check. Однако,
если do_check был бы закомментирован, то ограничение where T: 'x было бы требуется
на Checker.
Ограничения на ассоциированные типы также распространяют обязательные where-предложения.
#![allow(unused)] fn main() { trait Iterable { type Item<'a> where Self: 'a; type Iterator<'a>: Iterator<Item = Self::Item<'a>> where Self: 'a; fn iter<'a>(&'a self) -> Self::Iterator<'a>; } }
Здесь where Self: 'a требуется на Item из-за iter. Однако Item
используется в ограничениях Iterator, предложение where Self: 'a также требуется
там.
Наконец, любые явные использования 'static на GATs в трейте не учитываются в
обязательных ограничениях.
#![allow(unused)] fn main() { trait StaticReturn { type Y<'a>; fn foo(&self) -> Self::Y<'static>; } }
Ассоциированные константы
Ассоциированные константы - это константы, ассоциированные с типом.
Объявление ассоциированной константы объявляет сигнатуру для определений
ассоциированной константы. Оно записывается как const, затем идентификатор,
затем :, затем тип, завершаемый ;.
Идентификатор - это имя константы, используемое в пути. Тип - это тип, который определение должно реализовать.
Определение ассоциированной константы определяет константу, ассоциированную с типом. Оно записывается так же, как константный элемент.
Определения ассоциированных констант проходят вычисление констант только когда на них ссылаются. Более того, определения, которые включают обобщённые параметры, вычисляются после мономорфизации.
struct Struct; struct GenericStruct<const ID: i32>; impl Struct { // Определение не вычисляется немедленно const PANIC: () = panic!("compile-time panic"); } impl<const ID: i32> GenericStruct<ID> { // Определение не вычисляется немедленно const NON_ZERO: () = if ID == 0 { panic!("contradiction") }; } fn main() { // Ссылка на Struct::PANIC вызывает ошибку компиляции let _ = Struct::PANIC; // Нормально, ID не 0 let _ = GenericStruct::<1>::NON_ZERO; // Ошибка компиляции от вычисления NON_ZERO с ID=0 let _ = GenericStruct::<0>::NON_ZERO; }
Примеры ассоциированных констант
Базовый пример:
trait ConstantId { const ID: i32; } struct Struct; impl ConstantId for Struct { const ID: i32 = 1; } fn main() { assert_eq!(1, Struct::ID); }
Использование значений по умолчанию:
trait ConstantIdDefault { const ID: i32 = 1; } struct Struct; struct OtherStruct; impl ConstantIdDefault for Struct {} impl ConstantIdDefault for OtherStruct { const ID: i32 = 5; } fn main() { assert_eq!(1, Struct::ID); assert_eq!(5, OtherStruct::ID); }