关联项
语法
AssociatedItem :
外层属性* (
宏调用分号 (MacroInvocationSemi)
| ( 可见性? ( 类型别名 | 常量项 | 函数 ) )
)
关联项是在 trait 中声明或在 实现 中定义的项。它们之所以这样命名,是因为它们是在关联类型上定义的——即在实现中的类型。
它们是可以在模块中声明的项类型的一个子集。具体来说,有关联函数(包括方法)、关联类型和关联常量。
当关联项与关联项在逻辑上相关时,关联项就很有用。例如,Option 上的 is_some 方法与 Option 类型本身内在相关,因此应该关联起来。
每种关联项都有两种形式:包含实际实现的定义,以及声明定义的签名的声明。
正是这些声明构成了 trait 的契约以及泛型类型上可用的内容。
关联函数和方法
关联函数是与类型关联的函数。
关联函数声明声明了关联函数定义的签名。它的写法与函数项相同,只是函数体被 ; 替代。
标识符是函数的名称。
关联函数的泛型、参数列表、返回类型和 where 子句必须与关联函数声明的相同。
关联函数定义定义了一个与另一种类型关联的函数。它的写法与函数项相同。
一个常见的关联函数示例是 new 函数,它返回与关联函数关联的类型的某个值。
struct Struct { field: i32 } impl Struct { fn new() -> Struct { Struct { field: 0i32 } } } fn main () { let _struct = Struct::new(); }
当关联函数在 trait 上声明时,也可以通过一个路径来调用它,该路径是 trait 的路径后接 trait 的名称。发生这种情况时,它会被替换为 <_ as Trait>::function_name。
#![allow(unused)] fn main() { trait Num { fn from_i32(n: i32) -> Self; } impl Num for f64 { fn from_i32(n: i32) -> f64 { n as f64 } } // These 4 are all equivalent in this case. let _: f64 = Num::from_i32(42); let _: f64 = <_ as Num>::from_i32(42); let _: f64 = <f64 as Num>::from_i32(42); let _: f64 = f64::from_i32(42); }
方法
首个参数名为 self 的关联函数被称为方法,可以使用方法调用运算符(例如 x.foo())以及通常的函数调用表示法来调用。
如果指定了 self 参数的类型,它仅限于以下语法生成的类型(其中 'lt 表示某个任意生命周期):
P = &'lt S | &'lt mut S | Box<S> | Rc<S> | Arc<S> | Pin<P>
S = Self | P
此语法中的 Self 终结符表示解析为实现类型的类型。这也可以包括上下文类型别名 Self、其他类型别名或解析为实现类型的关联类型投影。
#![allow(unused)] fn main() { use std::rc::Rc; use std::sync::Arc; use std::pin::Pin; // Examples of methods implemented on struct `Example`. struct Example; type Alias = Example; trait Trait { type Output; } impl Trait for Example { type Output = Example; } impl Example { fn by_value(self: Self) {} fn by_ref(self: &Self) {} fn by_ref_mut(self: &mut Self) {} fn by_box(self: Box<Self>) {} fn by_rc(self: Rc<Self>) {} fn by_arc(self: Arc<Self>) {} fn by_pin(self: Pin<&Self>) {} fn explicit_type(self: Arc<Example>) {} fn with_lifetime<'a>(self: &'a Self) {} fn nested<'a>(self: &mut &'a Arc<Rc<Box<Alias>>>) {} fn via_projection(self: <Example as Trait>::Output) {} } }
可以使用不指定类型的简写语法,其等效形式如下:
| 简写 | 等效形式 |
|---|---|
self | self: Self |
&'lifetime self | self: &'lifetime Self |
&'lifetime mut self | self: &'lifetime mut Self |
注意
使用这种简写时,生命周期可以被(并且通常会被)省略。
如果 self 参数带有 mut 前缀,它会变成一个可变变量,类似于使用 mut 标识符模式的常规参数。例如:
#![allow(unused)] fn main() { trait Changer: Sized { fn change(mut self) {} fn modify(mut self: Box<Self>) {} } }
作为 trait 方法的示例,考虑以下内容:
#![allow(unused)] fn main() { type Surface = i32; type BoundingBox = i32; trait Shape { fn draw(&self, surface: Surface); fn bounding_box(&self) -> BoundingBox; } }
这定义了一个包含两个方法的 trait。所有在其作用域内实现了此 trait 的值都可以调用其 draw 和 bounding_box 方法。
#![allow(unused)] fn main() { type Surface = i32; type BoundingBox = i32; trait Shape { fn draw(&self, surface: Surface); fn bounding_box(&self) -> BoundingBox; } struct Circle { // ... } impl Shape for Circle { // ... fn draw(&self, _: Surface) {} fn bounding_box(&self) -> BoundingBox { 0i32 } } impl Circle { fn new() -> Circle { Circle{} } } let circle_shape = Circle::new(); let bounding_box = circle_shape.bounding_box(); }
版本差异:在 2015 版本中,可以在 trait 方法中声明匿名参数(例如
fn foo(u8))。此特性已弃用,并在 2018 版本中成为错误。所有参数都必须有参数名。
方法参数上的属性
方法参数上的属性遵循与普通函数参数相同的规则和限制。
关联类型
关联类型是与另一种类型关联的类型别名。
关联类型不能在固有实现中定义,也不能在 trait 中提供默认实现。
关联类型声明声明了关联类型定义的签名。它写成以下形式之一,其中 Assoc 是关联类型的名称,Params 是类型、生命周期或 const 参数的逗号分隔列表,Bounds 是关联类型必须满足的 trait 界限的加号分隔列表,WhereBounds 是参数必须满足的界限的逗号分隔列表:
type Assoc;
type Assoc: Bounds;
type Assoc<Params>;
type Assoc<Params>: Bounds;
type Assoc<Params> where WhereBounds;
type Assoc<Params>: Bounds where WhereBounds;
标识符是声明的类型别名的名称。
可选的 trait 界限必须由类型别名的实现来满足。
关联类型上有一个隐式的 Sized 界限,可以使用特殊的 ?Sized 界限来放宽。
关联类型定义为 trait 在类型上的实现定义了一个类型别名:
它们的写法类似于关联类型声明,但不能包含 Bounds,而必须包含一个 Type。
type Assoc = Type;
type Assoc<Params> = Type; // the type `Type` here may reference `Params`
type Assoc<Params> = Type where WhereBounds;
type Assoc<Params> where WhereBounds = Type; // deprecated, prefer the form above
如果类型 Item 有一个来自 trait Trait 的关联类型 Assoc,那么 <Item as Trait>::Assoc 是一种类型,它是关联类型定义中指定的类型的别名。
此外,如果 Item 是一个类型参数,那么 Item::Assoc 可以在类型参数中使用。
关联类型可以包含泛型参数和where 子句;这些通常被称为泛型关联类型,或 GAT。如果类型 Thing 有一个来自 trait Trait 的关联类型 Item 带有泛型 <'a>,那么该类型可以命名为 <Thing as Trait>::Item<'x>,其中 'x 是作用域内某个生命周期。在这种情况下,无论 'a 出现在 impl 上关联类型定义中的何处,都会使用 'x。
trait AssociatedType { // Associated type declaration type Assoc; } struct Struct; struct OtherStruct; impl AssociatedType for Struct { // Associated type definition type Assoc = OtherStruct; } impl OtherStruct { fn new() -> OtherStruct { OtherStruct } } fn main() { // Usage of the associated type to refer to OtherStruct as <Struct as AssociatedType>::Assoc let _other_struct: OtherStruct = <Struct as AssociatedType>::Assoc::new(); }
带有泛型和 where 子句的关联类型示例:
struct ArrayLender<'a, T>(&'a mut [T; 16]); trait Lend { // Generic associated type declaration type Lender<'a> where Self: 'a; fn lend<'a>(&'a mut self) -> Self::Lender<'a>; } impl<T> Lend for [T; 16] { // Generic associated type definition type Lender<'a> = ArrayLender<'a, T> where Self: 'a; fn lend<'a>(&'a mut self) -> Self::Lender<'a> { ArrayLender(self) } } fn borrow<'a, T: Lend>(array: &'a mut T) -> <T as Lend>::Lender<'a> { array.lend() } fn main() { let mut array = [0usize; 16]; let lender = borrow(&mut array); }
关联类型容器示例
考虑以下 Container trait 的示例。注意,该类型可以在方法签名中使用:
#![allow(unused)] fn main() { trait Container { type E; fn empty() -> Self; fn insert(&mut self, elem: Self::E); } }
为了使类型实现此 trait,它不仅必须为每个方法提供实现,还必须指定类型 E。下面是标准库类型 Vec 的 Container 实现:
#![allow(unused)] fn main() { trait Container { type E; fn empty() -> Self; fn insert(&mut self, elem: Self::E); } impl<T> Container for Vec<T> { type E = T; fn empty() -> Vec<T> { Vec::new() } fn insert(&mut self, x: T) { self.push(x); } } }
Bounds 与 WhereBounds 之间的关系
在此示例中:
#![allow(unused)] fn main() { use std::fmt::Debug; trait Example { type Output<T>: Ord where T: Debug; } }
给定关联类型的引用,如 <X as Example>::Output<Y>,关联类型本身必须是 Ord,并且类型 Y 必须是 Debug。
泛型关联类型上所需的 where 子句
trait 上的泛型关联类型声明目前可能需要一系列 where 子句,具体取决于 trait 中的函数以及 GAT 的使用方式。这些规则将来可能会放宽;有关更新,请参阅泛型关联类型倡议仓库。
简单来说,需要这些 where 子句是为了最大化 impl 中关联类型的允许定义。为此,任何在使用 GAT 作为输入或输出的函数(使用函数或 trait 的参数)中可以证明成立的子句,也必须写在 GAT 本身之上。
#![allow(unused)] fn main() { trait LendingIterator { type Item<'x> where Self: 'x; fn next<'a>(&'a mut self) -> Self::Item<'a>; } }
在上述示例中,在 next 函数上,我们可以证明 Self: 'a 成立,这是由于 &'a mut self 的隐式界限;因此,我们必须在 GAT 本身上写下等效的界限:where Self: 'x。
当 trait 中有多个函数使用 GAT 时,使用的是不同函数界限的交集,而不是并集。
#![allow(unused)] fn main() { trait Check<T> { type Checker<'x>; fn create_checker<'a>(item: &'a T) -> Self::Checker<'a>; fn do_check(checker: Self::Checker<'_>); } }
在此示例中,type Checker<'a>; 上不需要任何界限。虽然我们在 create_checker 上知道 T: 'a,但在 do_check 上不知道这一点。然而,如果 do_check 被注释掉,那么 where T: 'x 界限将需要在 Checker 上。
关联类型上的界限也会传播所需的 where 子句。
#![allow(unused)] fn main() { trait Iterable { type Item<'a> where Self: 'a; type Iterator<'a>: Iterator<Item = Self::Item<'a>> where Self: 'a; fn iter<'a>(&'a self) -> Self::Iterator<'a>; } }
在这里,由于 iter 的存在,where Self: 'a 在 Item 上是必需的。然而,Item 在 Iterator 的界限中使用,因此 where Self: 'a 子句在那里也是必需的。
最后,trait 中 GAT 上任何显式使用 'static 的情况不计入所需的界限。
#![allow(unused)] fn main() { trait StaticReturn { type Y<'a>; fn foo(&self) -> Self::Y<'static>; } }
关联常量
关联常量是与类型关联的常量。
关联常量声明声明了关联常量定义的签名。它的写法是 const,后跟一个标识符,再后跟 :,然后是一个类型,最后以 ; 结束。
标识符是在路径中使用的常量的名称。类型是定义必须实现的类型。
关联常量定义定义了一个与类型关联的常量。它的写法与常量项相同。
关联常量定义仅在引用时才进行常量求值。此外,包含泛型参数的定义在单态化 (monomorphization) 后进行求值。
struct Struct; struct GenericStruct<const ID: i32>; impl Struct { // Definition not immediately evaluated const PANIC: () = panic!("compile-time panic"); } impl<const ID: i32> GenericStruct<ID> { // Definition not immediately evaluated const NON_ZERO: () = if ID == 0 { panic!("contradiction") }; } fn main() { // Referencing Struct::PANIC causes compilation error let _ = Struct::PANIC; // Fine, ID is not 0 let _ = GenericStruct::<1>::NON_ZERO; // Compilation error from evaluating NON_ZERO with ID=0 let _ = GenericStruct::<0>::NON_ZERO; }
关联常量示例
一个基本示例:
trait ConstantId { const ID: i32; } struct Struct; impl ConstantId for Struct { const ID: i32 = 1; } fn main() { assert_eq!(1, Struct::ID); }
使用默认值:
trait ConstantIdDefault { const ID: i32 = 1; } struct Struct; struct OtherStruct; impl ConstantIdDefault for Struct {} impl ConstantIdDefault for OtherStruct { const ID: i32 = 5; } fn main() { assert_eq!(1, Struct::ID); assert_eq!(5, OtherStruct::ID); }