Ограничения трейтов и времён жизни
Syntax
TypeParamBounds → TypeParamBound ( + TypeParamBound )* +?
TypeParamBound → Lifetime | TraitBound | UseBound
TraitBound →
( ? | ForLifetimes )? TypePath
| ( ( ? | ForLifetimes )? TypePath )
LifetimeBounds → ( Lifetime + )* Lifetime?
Lifetime →
LIFETIME_OR_LABEL
| 'static
| '_
UseBound → use UseBoundGenericArgs
UseBoundGenericArgs →
< >
| < ( UseBoundGenericArg , )* UseBoundGenericArg ,? >
UseBoundGenericArg →
Lifetime
| IDENTIFIER
| Self
Ограничения трейтов и времён жизни предоставляют способ для обобщённых элементов ограничивать, какие типы и времена жизни используются в качестве их параметров. Ограничения могут быть указаны для любого типа в where clause. Также существуют сокращённые формы для некоторых распространённых случаев:
- Ограничения, записанные после объявления обобщённого параметра:
fn f<A: Copy>() {}эквивалентноfn f<A>() where A: Copy {}. - В объявлениях трейтов как супертрейты:
trait Circle : Shape {}эквивалентноtrait Circle where Self : Shape {}. - В объявлениях трейтов как ограничения на ассоциированные типы:
trait A { type B: Copy; }эквивалентноtrait A where Self::B: Copy { type B; }.
Ограничения на элемент должны выполняться при использовании элемента. При проверке типов и
заимствований обобщённого элемента ограничения могут использоваться для определения, что
трейт реализован для типа. Например, при условии Ty: Trait
- В теле обобщённой функции методы из
Traitмогут вызываться на значенияхTy. Аналогично могут использоваться ассоциированные константы изTrait. - Могут использоваться ассоциированные типы из
Trait. - Обобщённые функции и типы с ограничениями
T: Traitмогут использоваться сTyв качествеT.
#![allow(unused)] fn main() { type Surface = i32; trait Shape { fn draw(&self, surface: Surface); fn name() -> &'static str; } fn draw_twice<T: Shape>(surface: Surface, sh: T) { sh.draw(surface); // Можно вызвать метод, т.к. T: Shape sh.draw(surface); } fn copy_and_draw_twice<T: Copy>(surface: Surface, sh: T) where T: Shape { let shape_copy = sh; // не перемещает sh, потому что T: Copy draw_twice(surface, sh); // Можно использовать обобщённую функцию, т.к. T: Shape } struct Figure<S: Shape>(S, S); fn name_figure<U: Shape>( figure: Figure<U>, // Тип Figure<U> корректен, потому что U: Shape ) { println!( "Figure of two {}", U::name(), // Можно использовать ассоциированную функцию ); } }
Ограничения, которые не используют параметры элемента или времена жизни высшего ранга, проверяются при определении элемента. Ошибкой является ложность такого ограничения.
Ограничения Copy, Clone и Sized также проверяются для некоторых обобщённых типов при использовании элемента, даже если использование не предоставляет конкретный тип.
Ошибкой является наличие Copy или Clone в качестве ограничения для изменяемой ссылки, объекта трейта или среза.
Ошибкой является наличие Sized в качестве ограничения для объекта трейта или среза.
#![allow(unused)] fn main() { struct A<'a, T> where i32: Default, // Разрешено, но бесполезно i32: Iterator, // Ошибка: `i32` не является итератором &'a mut T: Copy, // (при использовании) Ошибка: ограничение трейта не выполнено [T]: Sized, // (при использовании) Ошибка: размер не может быть известен при компиляции { f: &'a T, } struct UsesA<'a, T>(A<'a, T>); }
Ограничения трейтов и времён жизни также используются для именования объектов трейтов.
?Sized
? используется только для ослабления неявного ограничения трейта Sized для параметров типа или ассоциированных типов.
?Sized не может использоваться как ограничение для других типов.
Ограничения времени жизни
Ограничения времени жизни могут применяться к типам или к другим временам жизни.
Ограничение 'a: 'b обычно читается как 'a переживает 'b.
'a: 'b означает, что 'a длится по крайней мере так же долго, как 'b, поэтому ссылка &'a () действительна всегда, когда действительна &'b ().
#![allow(unused)] fn main() { fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) where 'a: 'b { y = x; // &'a i32 является подтипом &'b i32, потому что 'a: 'b let r: &'b &'a i32 = &&0; // &'b &'a i32 корректен, потому что 'a: 'b } }
T: 'a означает, что все параметры времени жизни T переживают 'a.
Например, если 'a — это неограниченный параметр времени жизни, то i32: 'static и &'static str: 'a выполняются, но Vec<&'a ()>: 'static — нет.
Ограничения трейтов высшего ранга
Syntax
ForLifetimes → for GenericParams
Ограничения трейтов могут быть высшего ранга по временам жизни. Эти ограничения указывают условие,
которое выполняется для всех времён жизни. Например, ограничение вида for<'a> &'a T: PartialEq<i32> потребует реализации вида:
#![allow(unused)] fn main() { struct T; impl<'a> PartialEq<i32> for &'a T { // ... fn eq(&self, other: &i32) -> bool {true} } }
и затем может использоваться для сравнения &'a T с любым временем жизни и i32.
Только ограничение высшего ранга может быть использовано здесь, потому что время жизни ссылки короче любого возможного параметра времени жизни функции:
#![allow(unused)] fn main() { fn call_on_ref_zero<F>(f: F) where for<'a> F: Fn(&'a i32) { let zero = 0; f(&zero); } }
Времена жизни высшего ранга также могут указываться непосредственно перед трейтом: единственное различие — это область видимости параметра времени жизни, которая простирается только до конца следующего трейта, а не всего ограничения. Эта функция эквивалентна предыдущей.
#![allow(unused)] fn main() { fn call_on_ref_zero<F>(f: F) where F: for<'a> Fn(&'a i32) { let zero = 0; f(&zero); } }
Подразумеваемые ограничения
Иногда выводятся ограничения времени жизни, необходимые для корректности типов.
#![allow(unused)] fn main() { fn requires_t_outlives_a<'a, T>(x: &'a T) {} }
Для типа &'a T требуется, чтобы параметр типа T переживал 'a.
Это выводится, потому что сигнатура функции содержит тип &'a T, который
корректен только если выполняется T: 'a.
Подразумеваемые ограничения добавляются для всех параметров и выходных значений функций. Внутри requires_t_outlives_a
вы можете предполагать, что T: 'a выполняется, даже если вы не указали это явно:
#![allow(unused)] fn main() { fn requires_t_outlives_a_not_implied<'a, T: 'a>() {} fn requires_t_outlives_a<'a, T>(x: &'a T) { // Это компилируется, потому что `T: 'a` подразумевается // типом ссылки `&'a T`. requires_t_outlives_a_not_implied::<'a, T>(); } }
#![allow(unused)] fn main() { fn requires_t_outlives_a_not_implied<'a, T: 'a>() {} fn not_implied<'a, T>() { // Это ошибка, потому что `T: 'a` не подразумевается // сигнатурой функции. requires_t_outlives_a_not_implied::<'a, T>(); } }
Подразумеваются только ограничения времени жизни, ограничения трейтов всё ещё должны добавляться явно. Следующий пример поэтому вызывает ошибку:
#![allow(unused)] fn main() { use std::fmt::Debug; struct IsDebug<T: Debug>(T); // error[E0277]: `T` doesn't implement `Debug` fn doesnt_specify_t_debug<T>(x: IsDebug<T>) {} }
Ограничения времени жизни также выводятся для определений типов и блоков impl для любого типа:
#![allow(unused)] fn main() { struct Struct<'a, T> { // Это требует, чтобы `T: 'a` было корректно, // что выводится компилятором. field: &'a T, } enum Enum<'a, T> { // Это требует, чтобы `T: 'a` было корректно, // что выводится компилятором. // // Заметьте, что `T: 'a` требуется даже при использовании // только `Enum::OtherVariant`. SomeVariant(&'a T), OtherVariant, } trait Trait<'a, T: 'a> {} // Это вызвало бы ошибку, потому что `T: 'a` не подразумевается никаким типом // в заголовке impl. // impl<'a, T> Trait<'a, T> for () {} // Это компилируется, так как `T: 'a` подразумевается типом self `&'a T`. impl<'a, T> Trait<'a, T> for &'a T {} }
Use-ограничения
Некоторые списки ограничений могут включать ограничение use<..>, чтобы контролировать, какие обобщённые параметры захватываются абстрактным возвращаемым типом impl Trait. Смотрите precise capturing для подробностей.