Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Обобщённые параметры

Функции, псевдонимы типов, структуры, перечисления, объединения, трейты и реализации могут быть параметризованы типами, константами и временами жизни. Эти параметры перечисляются в угловых скобках (<...>), обычно сразу после имени элемента и перед его определением. Для реализаций, которые не имеют имени, они идут непосредственно после 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.

Параметры констант могут использоваться везде, где может использоваться константный элемент, за исключением того, что при использовании в типе или выражении повторения массива они должны быть автономными (как описано ниже). То есть они разрешены в следующих местах:

  1. Как применённая константа к любому типу, который является частью сигнатуры рассматриваемого элемента.
  2. Как часть константного выражения, используемого для определения ассоциированной константы, или как параметр для ассоциированного типа.
  3. Как значение в любом выражении времени выполнения в теле любых функций в элементе.
  4. Как параметр для любого типа, используемого в теле любых функций в элементе.
  5. Как часть типа любых полей в элементе.
#![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-предложения

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
}