Appearance
5.1 成员方法
trait 中可以定义函数。用例子来说明,我们定义如下的 trait:
rust
trait Shape {
fn area(&self) -> f64;
}上面这个 trait 包含了一个方法,这个方法只有一个参数,这个&self参数是什么意思呢?
所有的 trait 中都有一个隐藏的类型Self,代表当前这个实现了此 trait 的具体类型。 trait 中定义的函数,也可以称作关联函数(associated function)。 函数的第一个参数如果是Self相关的类型,且命名为self,这个参数可以被称为“receiver”(接收者)。 具有 receiver 参数的函数,我们称为“方法”(method),可以通过变量实例使用小数点.来调用。 没有 receiver 参数的函数,我们称为“静态函数”(static function),可以通过类型加双冒号::的方式来调用。 在 Rust 中,函数和方法没有本质区别。
Rust 中Self(大写 S)和self(小写 s)都是关键字,大写S的是类型名,小写s的是变量名。请大家一定注意区分。 self参数同样也可以指定类型,当然这个类型是有限制的,必须是包装在Self类型之上的类型。
对于第一个self参数,常见的类型有self: Self、self: &Self、self: &mut Self等类型。 对于以上这些类型,Rust 提供了一种简化的写法,我们可以将参数简写为self、&self、&mut self。
self 参数只能用在第一个参数的位置。 请注意“变量self”和“类型Self”的大小写不同。示例如下:
rust
trait T {
fn method1(self: Self);
fn method2(self: &Self);
fn method3(self: &mut Self);
}
// 上下两种写法是完全一样的
trait T {
fn method1(self);
fn method2(&self);
fn method3(&mut self);
}所以,回到开始定义的那个 Shape trait,上面定义的这个area方法的参数的名字为self,它的类型是&Self类型。我们可以把上面这个方法的声明看成:
rust
trait Shape {
fn area(self: &Self) -> f64;
}我们可以为某些具体类型实现(impl)这个 trait。
假如我们有一个结构体类型 Circle,它实现了这个 trait,代码如下:
rust
struct Circle {
radius: f64,
}
impl Shape for Circle {
// Self 类型就是 Circle
// self 的类型是 &Self,即 &Circle
fn area(&self) -> f64 {
// 访问成员变量,需要用 self.radius
std::f64::consts::PI * self.radius * self.radius
}
}
fn main() {
let c = Circle { radius : 2f64};
// 第一个参数名字是 self,可以使用小数点语法调用
println!("The area is {}", c.area());
}在上面的例子中可以看到,如果有一个 Circle 类型的实例c,我们就可以用小数点调用函数,c.area()。在方法内部,我们可以通过self.radius的方式访问类型的内部成员。
另外,针对一个类型,我们可以直接对它 impl 来增加成员方法,无须 trait 名字。比如:
rust
impl Circle {
fn get_radius(&self) -> f64 { self.radius }
}我们可以把这段代码看作是为 Circle 类型 impl 了一个匿名的 trait。用这种方式定义的方法叫作这个类型的“内在方法”(inherent methods)。
trait 中可以包含方法的默认实现。如果这个方法在 trait 中已经有了方法体,那么在针对具体类型实现的时候,就可以选择不用重写。当然,如果需要针对特殊类型作特殊处理,也可以选择重新实现来“override”默认的实现方式。比如,在标准库中,迭代器 Iterator 这个 trait 中就包含了十多个方法,但是,其中只有fn next(&mut self)-> Option<Self::Item>是没有默认实现的。其他的方法均有其默认实现,在实现迭代器的时候只需挑选需要重写的方法来实现即可。
self参数甚至可以是 Box 指针类型self: Box<Self>。另外,目前 Rust 设计组也在考虑让self变量的类型放得更宽,允许更多的自定义类型作为 receiver,比如MyType<Self>。示例如下:
rust
trait Shape {
fn area(self: Box<Self>) -> f64;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
// Self 类型就是 Circle
// self 的类型是 Box<Self>,即 Box<Circle>
fn area(self : Box<Self>) -> f64 {
// 访问成员变量,需要用 self.radius
std::f64::consts::PI * self.radius * self.radius
}
}
fn main() {
let c = Circle { radius : 2f64};
// 编译错误
// c.area();
let b = Box::new(Circle {radius : 4f64});
// 编译正确
b.area();
}impl 的对象甚至可以是 trait。示例如下:
rust
trait Shape {
fn area(&self) -> f64;
}
trait Round {
fn get_radius(&self) -> f64;
}
struct Circle {
radius: f64,
}
impl Round for Circle {
fn get_radius(&self) -> f64 { self.radius }
}
// 注意这里是 impl Trait for Trait
impl Shape for Round {
fn area(&self) -> f64 {
std::f64::consts::PI * self.get_radius() * self.get_radius()
}
}
fn main() {
let c = Circle { radius : 2f64};
// 编译错误
// c.area();
let b = Box::new(Circle {radius : 4f64}) as Box<Round>;
// 编译正确
b.area();
}注意这里的写法,impl Shape for Round和impl<T:Round>Shape for T是不一样的。在前一种写法中,self是&Round类型,它是一个 trait object,是胖指针。而在后一种写法中,self是&T类型,是具体类型。前一种写法是为 trait object 增加一个成员方法,而后一种写法是为所有的满足T: Round的具体类型增加一个成员方法。所以上面的示例中,我们只能构造一个 trait object 之后才能调用area()成员方法。trait object 和“泛型”之间的区别请参考本书第三部分。
题外话,impl Shape for Round这种写法确实是很让初学者纠结的,Round 既是 trait 又是 type。在将来,trait object 的语法会被要求加上dyn关键字,所以在 Rust 2018 edition 以后应该写成impl Shape for dyn Round才合理。关于 trait object 的内容,请参考本书第三部分第 23 章。