特性
语法
特性 :
unsafe
?trait
标识符 泛型参数? (:
类型参数界限? )? Where 子句?{
内部属性*
关联项*
}
特性描述了类型可以实现的抽象接口。这个接口由关联项组成,关联项有三种形式
特性声明在它所在模块或块的类型命名空间中定义一个特性。
关联项在各自的命名空间中被定义为特性的成员。关联类型在类型命名空间中定义。关联常量和关联函数在值命名空间中定义。
所有特性都定义了一个隐式的类型参数 Self
,它指向“正在实现此接口的类型”。特性也可以包含额外的类型参数。这些类型参数,包括 Self
,可以像通常一样,受到其他特性的约束。
通过单独的实现,特性可以针对特定类型来实现。
特性函数可以通过用分号替换函数体来省略函数体。这表示实现必须定义该函数。如果特性函数定义了一个函数体,则该定义充当任何未覆盖它的实现的默认值。类似地,关联常量可以省略等号和表达式,以指示实现必须定义常量值。关联类型永远不能定义类型,类型只能在实现中指定。
#![allow(unused)] fn main() { // Examples of associated trait items with and without definitions. trait Example { const CONST_NO_DEFAULT: i32; const CONST_WITH_DEFAULT: i32 = 99; type TypeNoDefault; fn method_without_default(&self); fn method_with_default(&self) {} } }
特性函数不允许是const
。
特性边界
泛型项可以使用特性作为其类型参数的边界。
泛型特性
可以为特性指定类型参数,使其成为泛型。这些参数出现在特性名称之后,使用与泛型函数中相同的语法。
#![allow(unused)] fn main() { trait Seq<T> { fn len(&self) -> u32; fn elt_at(&self, n: u32) -> T; fn iter<F>(&self, f: F) where F: Fn(T); } }
Dyn 兼容性
dyn 兼容的特性可以是trait 对象的基础特性。如果一个特性具有以下特性,则它是 dyn 兼容的
- 所有超特性也必须是 dyn 兼容的。
Sized
不能是超特性。换句话说,它不能要求Self: Sized
。
- 它不能有任何关联常量。
- 它不能有任何带有泛型的关联类型。
- 所有关联函数必须能够从 trait 对象分派,或者明确地不可分派
- 可分派的函数必须
- 没有任何类型参数(尽管允许生命周期参数)。
- 是一个方法,它不使用
Self
,除非在接收器的类型中。 - 接收器具有以下类型之一
- 没有不透明的返回类型;也就是说,
- 不是
async fn
(它有一个隐藏的Future
类型)。 - 没有返回位置
impl Trait
类型 (fn example(&self) -> impl Trait
)。
- 不是
- 没有
where Self: Sized
边界(Self
(即self
)的接收器类型意味着这一点)。
- 明确不可分派的函数需要
- 具有
where Self: Sized
边界(Self
(即self
)的接收器类型意味着这一点)。
- 具有
- 可分派的函数必须
注意:此概念以前被称为 对象安全。
#![allow(unused)] fn main() { use std::rc::Rc; use std::sync::Arc; use std::pin::Pin; // Examples of dyn compatible methods. trait TraitMethods { 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 with_lifetime<'a>(self: &'a Self) {} fn nested_pin(self: Pin<Arc<Self>>) {} } struct S; impl TraitMethods for S {} let t: Box<dyn TraitMethods> = Box::new(S); }
#![allow(unused)] fn main() { // This trait is dyn compatible, but these methods cannot be dispatched on a trait object. trait NonDispatchable { // Non-methods cannot be dispatched. fn foo() where Self: Sized {} // Self type isn't known until runtime. fn returns(&self) -> Self where Self: Sized; // `other` may be a different concrete type of the receiver. fn param(&self, other: Self) where Self: Sized {} // Generics are not compatible with vtables. fn typed<T>(&self, x: T) where Self: Sized {} } struct S; impl NonDispatchable for S { fn returns(&self) -> Self where Self: Sized { S } } let obj: Box<dyn NonDispatchable> = Box::new(S); obj.returns(); // ERROR: cannot call with Self return obj.param(S); // ERROR: cannot call with Self parameter obj.typed(1); // ERROR: cannot call with generic type }
#![allow(unused)] fn main() { use std::rc::Rc; // Examples of dyn-incompatible traits. trait DynIncompatible { const CONST: i32 = 1; // ERROR: cannot have associated const fn foo() {} // ERROR: associated function without Sized fn returns(&self) -> Self; // ERROR: Self in return type fn typed<T>(&self, x: T) {} // ERROR: has generic type parameters fn nested(self: Rc<Box<Self>>) {} // ERROR: nested receiver not yet supported } struct S; impl DynIncompatible for S { fn returns(&self) -> Self { S } } let obj: Box<dyn DynIncompatible> = Box::new(S); // ERROR }
#![allow(unused)] fn main() { // `Self: Sized` traits are dyn-incompatible. trait TraitWithSize where Self: Sized {} struct S; impl TraitWithSize for S {} let obj: Box<dyn TraitWithSize> = Box::new(S); // ERROR }
#![allow(unused)] fn main() { // Dyn-incompatible if `Self` is a type argument. trait Super<A> {} trait WithSelf: Super<Self> where Self: Sized {} struct S; impl<A> Super<A> for S {} impl WithSelf for S {} let obj: Box<dyn WithSelf> = Box::new(S); // ERROR: cannot use `Self` type parameter }
超特性
超特性是类型要实现特定特性所必须实现的特性。此外,任何泛型或trait 对象都受到特性的约束,它可以访问其超特性的关联项。
超特性通过特性的 Self
类型的特性边界以及在这些特性边界中声明的特性的传递超特性来声明。特性成为其自身的超特性是错误的。
具有超特性的特性被称为其超特性的 子特性。
以下是声明 Shape
为 Circle
的超特性的示例。
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle : Shape { fn radius(&self) -> f64; } }
以下是相同的示例,只不过使用了where 子句。
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64; } }
下一个示例使用 Shape
的 area
函数为 radius
提供默认实现。
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64 { // A = pi * r^2 // so algebraically, // r = sqrt(A / pi) (self.area() /std::f64::consts::PI).sqrt() } } }
下一个示例调用泛型参数上的超特性方法。
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle : Shape { fn radius(&self) -> f64; } fn print_area_and_radius<C: Circle>(c: C) { // Here we call the area method from the supertrait `Shape` of `Circle`. println!("Area: {}", c.area()); println!("Radius: {}", c.radius()); } }
类似地,以下是在 trait 对象上调用超特性方法的示例。
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle : Shape { fn radius(&self) -> f64; } struct UnitCircle; impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } } impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } } let circle = UnitCircle; let circle = Box::new(circle) as Box<dyn Circle>; let nonsense = circle.radius() * circle.area(); }
不安全特性
以 unsafe
关键字开头的特性项表示 实现 该特性可能是不安全的。使用正确实现的不安全特性是安全的。特性实现也必须以 unsafe
关键字开头。
参数模式
没有主体的函数或方法声明只允许标识符或 _
通配符模式。目前允许使用 mut
标识符,但它已被弃用,将来会成为硬性错误。
在 2015 版中,特性函数或方法参数的模式是可选的
#![allow(unused)] fn main() { // 2015 Edition trait T { fn f(i32); // Parameter identifiers are not required. } }
参数的模式种类限制为以下几种之一
从 2018 版开始,函数或方法参数模式不再是可选的。此外,只要有主体,就允许使用所有不可反驳的模式。如果没有主体,则上述限制仍然有效。
#![allow(unused)] fn main() { trait T { fn f1((a, b): (i32, i32)) {} fn f2(_: (i32, i32)); // Cannot use tuple pattern without a body. } }
项可见性
特性项在语法上允许可见性 注释,但在验证特性时会拒绝它。这允许在不同的使用上下文中,使用统一的语法解析项。例如,空的 vis
宏片段说明符可以用于特性项,其中宏规则可以用于允许可见性的其他情况。
macro_rules! create_method { ($vis:vis $name:ident) => { $vis fn $name(&self) {} }; } trait T1 { // Empty `vis` is allowed. create_method! { method_of_t1 } } struct S; impl S { // Visibility is allowed here. create_method! { pub method_of_s } } impl T1 for S {} fn main() { let s = S; s.method_of_t1(); s.method_of_s(); }