traits.md
commit: 20340ce30db8ec17b0a09dc6e07c0fa2f5c3c0ab
本章译文最后维护日期:2021-5-6
句法
Trait :
unsafe
? trait
IDENTIFIER
GenericParams?
( :
TypeParamBounds? )?
WhereClause? {
InnerAttribute*
AssociatedItem*
}
trait 描述类型可以实现的抽象接口。这类接口由三种关联程序项(associated items)组成,它们分别是:
所有 trait 都定义了一个隐式类型参数 Self
,它指向“实现此接口的类型”。trait 还可能包含额外的类型参数。这些类型参数,包括 Self
在内,都可能会跟正常类型参数一样受到其他 trait 的约束。
trait 需要具体的类型去实现,具体的实现方法是通过该类型的各种独立实现(implementations)来完成的。
trait函数可以通过使用分号代替函数体来省略函数体。这表明此 trait的实现必须去定义实现该函数。如果 trait函数定义了一个函数体,那么这个定义就会作为任何不覆盖它的实现的默认函数实现。类似地,关联常量可以省略等号和表达式,以指示相应的实现必须定义该常量值。关联类型不能定义类型,只能在实现中指定类型。
#![allow(unused)]
fn main() {
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) {}
}
}
Trait函数不能是 async
或 const
类型的。
泛型程序项可以使用 trait 作为其类型参数的约束。
可以为 trait 指定类型参数来使该 trait 成为泛型trait/泛型类型。这些类型参数出现在 trait 名称之后,使用与泛型函数相同的句法。
#![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);
}
}
对象安全的 trait 可以是 trait对象的底层 trait。如果 trait 符合以下限定条件(在 RFC 255 中定义),则认为它是对象安全的(object safe):
- 所有的超类traitsupertraits 也必须也是对象安全的。
- 超类trait 中不能有
Sized
。也就是说不能有 Self: Sized
约束。
- 它必须没有任何关联常量。
- 所有关联函数必须可以从 trait对象调度分派,或者是显式不可调度分派:
- 可调度分派函数要求:
- 不能有类型参数(尽管生存期参数可以有)
- 作为方法时,
Self
只能出现在方法的接受者(receiver)的类型里,其它地方不能使用 Self
。
- 方法的接受者的类型必须是以下类型之一:
- 没有
where Self: Sized
约束(即接受者的类型 Self
(例如:self
) 不能有 Sized
约束)。
- 显式不可调度分派函数要求:
- 有
where Self: Sized
约束(即接受者的类型 Self
(例如:self
) 有 Sized
约束)。
#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
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() {
trait NonDispatchable {
fn foo() where Self: Sized {}
fn returns(&self) -> Self where Self: Sized;
fn param(&self, other: Self) where Self: Sized {}
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();
obj.param(S);
obj.typed(1);
}
#![allow(unused)]
fn main() {
use std::rc::Rc;
trait NotObjectSafe {
const CONST: i32 = 1;
fn foo() {}
fn returns(&self) -> Self;
fn typed<T>(&self, x: T) {}
fn nested(self: Rc<Box<Self>>) {}
}
struct S;
impl NotObjectSafe for S {
fn returns(&self) -> Self { S }
}
let obj: Box<dyn NotObjectSafe> = Box::new(S);
}
#![allow(unused)]
fn main() {
trait TraitWithSize where Self: Sized {}
struct S;
impl TraitWithSize for S {}
let obj: Box<dyn TraitWithSize> = Box::new(S);
}
#![allow(unused)]
fn main() {
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);
}
超类trait 是类型为了实现某特定 trait 而需要一并实现的 trait。此外,在任何地方,如果泛型或 trait对象被某个 trait约束,那这个泛型或 trait对象就可以访问这个超类trait 的关联程序项。
超类trait 是通过 trait 的 Self
类型上的 trait约束来声明的,并且通过这种声明 trait约束的方式来传递这种超类trait 关系。一个 trait 不能是它自己的超类trait。
有超类trait 的 trait 称其为其超类trait 的子trait(subtrait)。
下面是一个声明 Shape
是 Circle
的超类trait 的例子。
#![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 {
(self.area() /std::f64::consts::PI).sqrt()
}
}
}
下一个示例调用了一个泛型参数的超类trait 上的方法。
#![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) {
println!("Area: {}", c.area());
println!("Radius: {}", c.radius());
}
}
类似地,这里是一个在 trait对象上调用超类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
开头的 trait程序项表示实现该 trait 可能是非安全的。使用正确实现的非安全trait 是安全的。trait实现也必须以关键字 unsafe
开头。
Sync
和 Send
是典型的非安全trait。
(trait 中)没有代码体的函数声明或方法声明(的参数模式)只允许使用标识符/IDENTIFIER模式 或 _
通配符模式。当前 mut
IDENTIFIER 还是允许的,但已被弃用,未来将成为一个硬编码错误(hard error)。
在 2015 版中,trait 的函数或方法的参数模式是可选的:
#![allow(unused)]
fn main() {
trait T {
fn f(i32);
}
}
所有的参数模式被限制为下述之一:
(跟普通函数一样,)从 2018 版开始,(trait 中)函数或方法的参数模式不再是可选的。同时,也跟普通函数一样,(trait 中)函数或方法只要有代码体,其参数模式可以是任何不可反驳型模式。但如果没有代码体,上面列出的限制仍然有效。
#![allow(unused)]
fn main() {
trait T {
fn f1((a, b): (i32, i32)) {}
fn f2(_: (i32, i32));
}
}
依照句法规定,trait程序项在语法上允许使用 Visibility句法的注释,但是当 trait 被(句法法分析程序)验证(validate)后,该可见性注释又被弃用。因此,在源码解析层面,可以在使用程序项的不同上下文中使用统一的语法对这些程序项进行解析。例如,空的 vis
宏匹配段选择器可以用于 trait程序项,而在其他允许使用非空可见性的情况下,也可使用这同一套宏规则。
macro_rules! create_method {
($vis:vis $name:ident) => {
$vis fn $name(&self) {}
};
}
trait T1 {
create_method! { method_of_t1 }
}
struct S;
impl S {
create_method! { pub method_of_s }
}
impl T1 for S {}
fn main() {
let s = S;
s.method_of_t1();
s.method_of_s();
}