Обобщённые параметры
Syntax
GenericParams → < ( GenericParam ( , GenericParam )* ,? )? >
GenericParam → OuterAttribute* ( LifetimeParam | TypeParam | ConstParam )
LifetimeParam → Lifetime ( : LifetimeBounds )?
TypeParam → IDENTIFIER ( : TypeParamBounds? )? ( = Type )?
ConstParam →
const IDENTIFIER : Type
( = ( BlockExpression | IDENTIFIER | -? LiteralExpression ) )?
Функции, псевдонимы типов, структуры, перечисления, объединения, трейты и
реализации могут быть параметризованы типами, константами и временами жизни. Эти
параметры перечисляются в угловых скобках (<...>),
обычно сразу после имени элемента и перед его определением. Для
реализаций, которые не имеют имени, они идут непосредственно после impl.
Порядок обобщённых параметров ограничен: сначала параметры времени жизни, затем параметры типов и констант вперемешку.
Одно и то же имя параметра не может быть объявлено более одного раза в списке GenericParams.
Некоторые примеры элементов с параметрами типов, констант и времён жизни:
#![allow(unused)] fn main() { fn foo<'a, T>() {} trait A<U> {} struct Ref<'a, T> where T: 'a { r: &'a T } struct InnerArray<T, const N: usize>([T; N]); struct EitherOrderWorks<const N: bool, U>(U); }
Обобщённые параметры находятся в области видимости внутри определения элемента, где они объявлены. Они не находятся в области видимости для элементов, объявленных внутри тела функции, как описано в объявлениях элементов. См. области видимости обобщённых параметров для получения более подробной информации.
Ссылки, сырые указатели, массивы, срезы, кортежи и указатели на функции также имеют параметры времени жизни или типов, но не ссылаются на них с помощью синтаксиса путей.
'_ и 'static не являются допустимыми именами параметров времени жизни.
Константные обобщения
Параметры константных обобщений позволяют элементам быть обобщёнными по значениям констант.
Идентификатор константы вводит имя в пространстве имён значений для параметра константы, и все экземпляры элемента должны быть созданы со значением заданного типа.
Единственные допустимые типы для параметров констант: u8, u16, u32, u64, u128, usize,
i8, i16, i32, i64, i128, isize, char и bool.
Параметры констант могут использоваться везде, где может использоваться константный элемент, за исключением того, что при использовании в типе или выражении повторения массива они должны быть автономными (как описано ниже). То есть они разрешены в следующих местах:
- Как применённая константа к любому типу, который является частью сигнатуры рассматриваемого элемента.
- Как часть константного выражения, используемого для определения ассоциированной константы, или как параметр для ассоциированного типа.
- Как значение в любом выражении времени выполнения в теле любых функций в элементе.
- Как параметр для любого типа, используемого в теле любых функций в элементе.
- Как часть типа любых полей в элементе.
#![allow(unused)] fn main() { // Примеры, где могут использоваться параметры константных обобщений. // Используется в сигнатуре самого элемента. fn foo<const N: usize>(arr: [i32; N]) { // Используется как тип внутри тела функции. let x: [i32; N]; // Используется как выражение. println!("{}", N * 2); } // Используется как поле структуры. struct Foo<const N: usize>([i32; N]); impl<const N: usize> Foo<N> { // Используется как ассоциированная константа. const CONST: usize = N * 4; } trait Trait { type Output; } impl<const N: usize> Trait for Foo<N> { // Используется как ассоциированный тип. type Output = [i32; N]; } }
#![allow(unused)] fn main() { // Примеры, где параметры константных обобщений не могут использоваться. fn foo<const N: usize>() { // Нельзя использовать в определениях элементов внутри тела функции. const BAD_CONST: [usize; N] = [1; N]; static BAD_STATIC: [usize; N] = [1; N]; fn inner(bad_arg: [usize; N]) { let bad_value = N * 2; } type BadAlias = [usize; N]; struct BadStruct([usize; N]); } }
В качестве дополнительного ограничения, параметры констант могут появляться только как автономные
аргументы внутри типа или выражения повторения массива. В этих контекстах
они могут использоваться только как выражение пути с одним сегментом, возможно внутри
блока (такого как N или {N}). То есть они не могут комбинироваться с другими
выражениями.
#![allow(unused)] fn main() { // Примеры, где параметры констант не могут использоваться. // Не разрешено комбинировать в других выражениях в типах, таких как // арифметическое выражение в типе возврата здесь. fn bad_function<const N: usize>() -> [u8; {N + 1}] { // Аналогично не разрешено для выражений повторения массива. [1; {N + 1}] } }
Аргумент константы в пути указывает значение константы для использования в этом элементе.
Аргумент должен быть либо выведенной константой, либо константным выражением типа, приписанного к параметру константы. Константное выражение должно быть блочным выражением (окружённым фигурными скобками), если только это не одиночный сегмент пути (IDENTIFIER) или литерал (с возможным ведущим токеном -).
Note
Это синтаксическое ограничение необходимо, чтобы избежать необходимости бесконечного просмотра вперёд при разборе выражения внутри типа.
#![allow(unused)] fn main() { struct S<const N: i64>; const C: i64 = 1; fn f<const N: i64>() -> S<N> { S } let _ = f::<1>(); // Литерал. let _ = f::<-1>(); // Отрицательный литерал. let _ = f::<{ 1 + 2 }>(); // Константное выражение. let _ = f::<C>(); // Одиночный сегмент пути. let _ = f::<{ C + 1 }>(); // Константное выражение. let _: S<1> = f::<_>(); // Выведенная константа. let _: S<1> = f::<(((_)))>(); // Выведенная константа. }
Note
В списке обобщённых аргументов выведенная константа разбирается как выведенный тип, но затем семантически обрабатывается как отдельный вид аргумента константного обобщения.
Там, где ожидается аргумент константы, может использоваться _ (опционально окружённый любым количеством соответствующих скобок), называемый выведенной константой (правила путей, правила выражений массива), вместо этого. Это просит компилятор вывести аргумент константы, если это возможно, на основе окружающей информации.
#![allow(unused)] fn main() { fn make_buf<const N: usize>() -> [u8; N] { [0; _] // ^ Выводит `N`. } let _: [u8; 1024] = make_buf::<_>(); // ^ Выводит `1024`. }
Note
Выведенная константа семантически не является выражением и поэтому не принимается внутри фигурных скобок.
#![allow(unused)] fn main() { fn f<const N: usize>() -> [u8; N] { [0; _] } let _: [_; 1] = f::<{ _ }>(); // ^ ОШИБКА: `_` не разрешён здесь }
Выведенная константа не может использоваться в сигнатурах элементов.
#![allow(unused)] fn main() { fn f<const N: usize>(x: [u8; N]) -> [u8; _] { x } // ^ ОШИБКА: не разрешено }
Когда есть неоднозначность, может ли обобщённый аргумент быть разрешён как аргумент типа или константы, он всегда разрешается как тип. Размещение аргумента в блочном выражении может заставить его интерпретироваться как аргумент константы.
#![allow(unused)] fn main() { type N = u32; struct Foo<const N: usize>; // Следующее является ошибкой, потому что `N` интерпретируется как псевдоним типа `N`. fn foo<const N: usize>() -> Foo<N> { todo!() } // ОШИБКА // Можно исправить, обернув в фигурные скобки, чтобы заставить интерпретировать как параметр // константы `N`: fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok }
В отличие от параметров типов и времён жизни, параметры констант могут быть объявлены без использования внутри параметризованного элемента, за исключением реализаций, как описано в обобщённых реализациях:
#![allow(unused)] fn main() { // ok struct Foo<const N: usize>; enum Bar<const M: usize> { A, B } // ОШИБКА: неиспользуемый параметр struct Baz<T>; struct Biz<'a>; struct Unconstrained; impl<const N: usize> Unconstrained {} }
При разрешении обязательства ограничения трейта, исчерпываемость всех
реализаций параметров констант не учитывается при определении, удовлетворено ли
ограничение. Например, в следующем, даже though все возможные
значения констант для типа bool реализованы, это всё равно ошибка, что
ограничение трейта не удовлетворено:
#![allow(unused)] fn main() { struct Foo<const B: bool>; trait Bar {} impl Bar for Foo<true> {} impl Bar for Foo<false> {} fn needs_bar(_: impl Bar) {} fn generic<const B: bool>() { let v = Foo::<B>; needs_bar(v); // ОШИБКА: ограничение трейта `Foo<B>: Bar` не удовлетворено } }
Where-предложения
Syntax
WhereClause → where ( WhereClauseItem , )* WhereClauseItem?
WhereClauseItem →
LifetimeWhereClauseItem
| TypeBoundWhereClauseItem
LifetimeWhereClauseItem → Lifetime : LifetimeBounds
TypeBoundWhereClauseItem → ForLifetimes? Type : TypeParamBounds?
Where-предложения предоставляют другой способ указания ограничений на параметры типов и времён жизни, а также способ указания ограничений на типы, которые не являются параметрами типов.
Ключевое слово for может использоваться для введения ранжированных времён жизни. Оно
разрешает только параметры LifetimeParam.
#![allow(unused)] fn main() { struct A<T> where T: Iterator, // Можно использовать A<T: Iterator> вместо этого T::Item: Copy, // Ограничение на ассоциированный тип String: PartialEq<T>, // Ограничение на `String`, используя параметр типа i32: Default, // Разрешено, но бесполезно { f: T, } }
Атрибуты
Параметры обобщённых времён жизни и типов допускают атрибуты на них. Нет встроенных атрибутов, которые что-либо делают в этой позиции, хотя пользовательские атрибуты производных могут придавать этому значение.
Этот пример показывает использование пользовательского атрибута производного для изменения значения обобщённого параметра.
// Предположим, что derive для MyFlexibleClone объявил `my_flexible_clone` как
// атрибут, который он понимает.
#[derive(MyFlexibleClone)]
struct Foo<#[my_flexible_clone(unbounded)] H> {
a: *const H
}