Реализации
Syntax
Implementation → InherentImpl | TraitImpl
InherentImpl →
impl GenericParams? Type WhereClause? {
InnerAttribute*
AssociatedItem*
}
TraitImpl →
unsafe? impl GenericParams? !? TypePath for Type
WhereClause?
{
InnerAttribute*
AssociatedItem*
}
Реализация - это элемент, который связывает элементы с реализующим типом.
Реализации определяются с ключевым словом impl и содержат функции,
которые принадлежат экземпляру типа, который реализуется, или типу
статически.
Существует два типа реализаций:
- собственные реализации (inherent implementations)
- реализации трейтов
Собственные реализации
Собственная реализация определяется как последовательность ключевого слова impl,
объявлений обобщённых типов, пути к именованному типу, where-предложения и
набора ассоциированных элементов в фигурных скобках.
Именованный тип называется реализующим типом, а ассоциированные элементы являются ассоциированными элементами реализующего типа.
Собственные реализации связывают содержащиеся элементы с реализующим типом.
Собственные реализации могут содержать ассоциированные функции (включая методы) и ассоциированные константы.
Они не могут содержать ассоциированные псевдонимы типов.
Путь к ассоциированному элементу - это любой путь к реализующему типу, за которым следует идентификатор ассоциированного элемента как конечный компонент пути.
Тип также может иметь несколько собственных реализаций. Реализующий тип должен быть определён в том же крейте, что и исходное определение типа.
pub mod color { pub struct Color(pub u8, pub u8, pub u8); impl Color { pub const WHITE: Color = Color(255, 255, 255); } } mod values { use super::color::Color; impl Color { pub fn red() -> Color { Color(255, 0, 0) } } } pub use self::color::Color; fn main() { // Фактический путь к реализующему типу и реализации в том же модуле. color::Color::WHITE; // Блоки реализации в разных модулях всё ещё доступны через путь к типу. color::Color::red(); // Переэкспортированные пути к реализующему типу также работают. Color::red(); // Не работает, потому что use в `values` не является pub. // values::Color::red(); }
Реализации трейтов
Реализация трейта определяется так же, как собственная реализация, за исключением того, что
опциональные объявления обобщённых типов следуют после трейта, затем
следует ключевое слово for, затем путь к именованному типу.
Трейт известен как реализуемый трейт. Реализующий тип реализует реализуемый трейт.
Реализация трейта должна определить все не-по-умолчанию ассоциированные элементы, объявленные реализуемым трейтом, может переопределить ассоциированные элементы по умолчанию, определённые реализуемым трейтом, и не может определять никакие другие элементы.
Путь к ассоциированным элементам - это <, затем путь к реализующему
типу, затем as, затем путь к трейту, затем > как компонент пути,
за которым следует компонент пути ассоциированного элемента.
Небезопасные трейты требуют, чтобы реализация трейта начиналась с ключевого слова unsafe.
#![allow(unused)] fn main() { #[derive(Copy, Clone)] struct Point {x: f64, y: f64}; type Surface = i32; struct BoundingBox {x: f64, y: f64, width: f64, height: f64}; trait Shape { fn draw(&self, s: Surface); fn bounding_box(&self) -> BoundingBox; } fn do_draw_circle(s: Surface, c: Circle) { } struct Circle { radius: f64, center: Point, } impl Copy for Circle {} impl Clone for Circle { fn clone(&self) -> Circle { *self } } impl Shape for Circle { fn draw(&self, s: Surface) { do_draw_circle(s, *self); } fn bounding_box(&self) -> BoundingBox { let r = self.radius; BoundingBox { x: self.center.x - r, y: self.center.y - r, width: 2.0 * r, height: 2.0 * r, } } } }
Когерентность реализации трейта
Реализация трейта считается некогерентной, если либо проверка правил сиротности не проходит, либо есть перекрывающиеся экземпляры реализации.
Две реализации трейта перекрываются, когда есть непустое пересечение трейтов, для которых реализация предназначена, и реализации могут быть инстанцированы одним и тем же типом.
Правила сиротности
Правило сиротности гласит, что реализация трейта разрешена только если либо трейт, либо хотя бы один из типов в реализации определён в текущем крейте. Оно предотвращает конфликтующие реализации трейтов в разных крейтах и является ключевым для обеспечения когерентности.
Сиротская реализация - это реализация, которая реализует иностранный трейт для иностранного типа. Если бы они были свободно разрешены, два крейта могли бы реализовать один и тот же трейт для одного и того же типа несовместимыми способами, создавая ситуацию, когда добавление или обновление зависимости могло бы сломать компиляцию из-за конфликтующих реализаций.
Правило сиротности позволяет авторам библиотек добавлять новые реализации к своим трейтам без страха, что они сломают нижестоящий код. Без этих ограничений библиотека не могла бы добавить реализацию типа impl<T: Display> MyTrait for T без потенциального конфликта с нижестоящими реализациями.
Для impl<P1..=Pn> Trait<T1..=Tn> for T0, impl действителен только если
хотя бы одно из следующего верно:
Traitявляется локальным трейтом- Все из
- Хотя бы один из типов
T0..=Tnдолжен быть локальным типом. ПустьTiбудет первым таким типом. - Никакие непокрытые параметры типа
P1..=Pnне могут появляться вT0..Ti(исключаяTi)
- Хотя бы один из типов
Только появление непокрытых параметров типа ограничено.
Заметим, что для целей когерентности фундаментальные типы являются
особыми. T в Box<T> не считается покрытым, и Box<LocalType>
считается локальным.
Обобщённые реализации
Реализация может принимать обобщённые параметры, которые могут использоваться в остальной
части реализации. Параметры реализации записываются непосредственно после
ключевого слова impl.
#![allow(unused)] fn main() { trait Seq<T> { fn dummy(&self, _: T) { } } impl<T> Seq<T> for Vec<T> { /* ... */ } impl Seq<bool> for u32 { /* Рассматриваем целое число как последовательность битов */ } }
Обобщённые параметры ограничивают реализацию, если параметр появляется хотя бы один раз в одном из:
- Реализуемом трейте, если он есть
- Реализующем типе
- Как ассоциированный тип в ограничениях типа, который содержит другой параметр, который ограничивает реализацию
Параметры типа и констант должны всегда ограничивать реализацию. Времена жизни должны ограничивать реализацию, если время жизни используется в ассоциированном типе.
Примеры ограничивающих ситуаций:
#![allow(unused)] fn main() { trait Trait{} trait GenericTrait<T> {} trait HasAssocType { type Ty; } struct Struct; struct GenericStruct<T>(T); struct ConstGenericStruct<const N: usize>([(); N]); // T ограничивает, будучи аргументом GenericTrait. impl<T> GenericTrait<T> for i32 { /* ... */ } // T ограничивает, будучи аргументом GenericStruct impl<T> Trait for GenericStruct<T> { /* ... */ } // Аналогично, N ограничивает, будучи аргументом ConstGenericStruct impl<const N: usize> Trait for ConstGenericStruct<N> { /* ... */ } // T ограничивает, будучи в ассоциированном типе в ограничении для типа `U`, который // сам является обобщённым параметром, ограничивающим трейт. impl<T, U> GenericTrait<U> for u32 where U: HasAssocType<Ty = T> { /* ... */ } // Как предыдущий, кроме того, что тип - `(U, isize)`. `U` появляется внутри типа, // который включает `T`, и не является самим типом. impl<T, U> GenericStruct<U> where (U, isize): HasAssocType<Ty = T> { /* ... */ } }
Примеры неограничивающих ситуаций:
#![allow(unused)] fn main() { // Остальные из этих являются ошибками, поскольку они имеют параметры типа или констант, которые // не ограничивают. // T не ограничивает, поскольку не появляется вообще. impl<T> Struct { /* ... */ } // N не ограничивает по той же причине. impl<const N: usize> Struct { /* ... */ } // Использование T внутри реализации не ограничивает impl. impl<T> Struct { fn uses_t(t: &T) { /* ... */ } } // T используется как ассоциированный тип в ограничениях для U, но U не ограничивает. impl<T, U> Struct where U: HasAssocType<Ty = T> { /* ... */ } // T используется в ограничениях, но не как ассоциированный тип, поэтому не ограничивает. impl<T, U> GenericTrait<U> for u32 where U: GenericTrait<T> {} }
Пример разрешённого неограничивающего параметра времени жизни:
#![allow(unused)] fn main() { struct Struct; impl<'a> Struct {} }
Пример запрещённого неограничивающего параметра времени жизни:
#![allow(unused)] fn main() { struct Struct; trait HasAssocType { type Ty; } impl<'a> HasAssocType for Struct { type Ty = &'a Struct; } }
Атрибуты на реализациях
Реализации могут содержать внешние атрибуты перед ключевым словом impl и
внутренние атрибуты внутри скобок, которые содержат ассоциированные элементы. Внутренние
атрибуты должны идти перед любыми ассоциированными элементами. Атрибуты, которые имеют
значение здесь, это cfg, deprecated, doc и атрибуты проверки линтов.