类型参数和生存期参数
generics.md
commit: 7fd47ef4786f86ccdb5d6f0f198a6a9fdec5497c
本章译文最后维护日期:2021-1-17
句法
GenericParams :
<
>
|<
(GenericParam,
)* GenericParam,
?>
GenericParam :
[OuterAttribute]* ( LifetimeParam | TypeParam | ConstParam )LifetimeParam :
[LIFETIME_OR_LABEL] (:
[LifetimeBounds] )?TypeParam :
[IDENTIFIER](:
[TypeParamBounds]? )? (=
[Type] )?ConstParam:
const
[IDENTIFIER]:
[Type]
函数、类型别名、结构体、枚举、联合体、trait 和实现可以通过类型参数、常量参数和生存期参数达到参数化配置的的效果。这些参数在尖括号(<...>
)中列出,通常都是紧跟在程序项名称之后和程序项的定义之前。对于实现,因为它没有名称,那它们就直接位于关键字 impl
之后。泛型参数的申明顺序是生存期参数在最前面,然后是类型参数,最后是常量参数。
下面给出一些带类型参数、常量参数和生存期参数的程序项的示例:
#![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]); }
泛型参数在声明它们的程序项定义的范围内有效。它们不是函数体中声明的程序项,这个在程序项声明中有讲述。
引用、裸指针、数组、切片、元组和函数指针也有生存期参数或类型参数,但这些程序项不能使用路径句法去引用。
常量泛型
常量泛型参数允许程序项在常量值上泛型化。const标识符为常量参数引入了一个名称,并且该程序项的所有实例必须用给定类型的值去实例化该参数。
常量参数类型值允许为: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])或一个字面量(此字面量可以是以 -
打头的 token)。
注意:这种句法限制是必要的,用以避免在解析类型内部的表达式时可能会导致无限递归(infinite lookahead)。
#![allow(unused)] fn main() { fn double<const N: i32>() { println!("doubled: {}", N * 2); } const SOME_CONST: i32 = 12; fn example() { // 常量参数的使用示例。 double::<9>(); double::<-123>(); double::<{7 + 8}>(); double::<SOME_CONST>(); double::<{ SOME_CONST + 5 }>(); } }
当存在歧义时,如果泛型参数可以同时被解析为类型或常量参数,那么它总是被解析为类型。在块表达式中放置实参可以强制将其解释为常量实参。
#![allow(unused)] fn main() { type N = u32; struct Foo<const N: usize>; // 下面用法是错误的,因为 `N` 被解释为类型别名。 fn foo<const N: usize>() -> Foo<N> { todo!() } // ERROR // 可以使用花括号来强制将 `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 } // ERROR: 参数未使用 struct Baz<T>; struct Biz<'a>; struct Unconstrained; impl<const N: usize> Unconstrained {} }
当处理 trait约束时,在确定是否满足相关约束时,不会考虑常量参数的所有实现的穷尽性。例如,在下面的例子中,即使实现了 bool
类型的所有可能的常量值,仍会报错提示 trait约束不满足。
#![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); // ERROR: trait约束 `Foo<B>: Bar` 未被满足 } }
where子句
句法
WhereClause :
where
( WhereClauseItem,
)* WhereClauseItem ?WhereClauseItem :
LifetimeWhereClauseItem
| TypeBoundWhereClauseItemLifetimeWhereClauseItem :
[Lifetime]:
[LifetimeBounds]TypeBoundWhereClauseItem :
ForLifetimes? [Type]:
[TypeParamBounds]?ForLifetimes :
for
<
GenericParams>
where子句提供了另一种方法来为类型参数和生存期参数指定约束(bound),甚至可以为非类型参数的类型指定约束。
关键字for
可以用来引入高阶生存期参数。它只允许在 [LifetimeParam] 参数上使用。
定义程序项时,其约束没有使用程序项的泛型参数或高阶生存期参数,这样是可以通过编译器的安全检查,但这样的做法将必然导致错误。
在定义程序项时,编译器还会检查某些泛型参数的类型是否存在 Copy
、Clone
和 Sized
这些约束。将Copy
或 Clone
作为可变引用、trait object或slice这些程序项的约束是错误的,或将 Sized
作为 trait对象或切片的约束也是错误的。
#![allow(unused)] fn main() { struct A<T> where T: Iterator, // 可以用 A<T: Iterator> 来替代 T::Item: Copy, String: PartialEq<T>, i32: Default, // 允许,但没什么用 i32: Iterator, // 错误: 此 trait约束不适合,i32 没有实现 Iterator [T]: Copy, // 错误: 此 trait约束不适合,切片上不能有此 trait约束 { f: T, } }
属性
泛型生存期参数和泛型类型参数允许属性,但在目前这个位置还没有任何任何有意义的内置属性,但用户可能可以通过自定义的派生属性来设置一些有意义的属性。
下面示例演示如何使用自定义派生属性修改泛型参数的含义。
// 假设 MyFlexibleClone 的派生项将 `my_flexible_clone` 声明为它可以理解的属性。
#[derive(MyFlexibleClone)]
struct Foo<#[my_flexible_clone(unbounded)] H> {
a: *const H
}