成员方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| trait Shape { fn area(&self) -> f64;//关联函数,参数被称为接收者 }
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); }
|
具有receiver参数的函数,我们称为“方法”(method),可以通过变量实例使用小数点来调用。
没有receiver参数的函数,我们称为“静态函数”(static function),可以通过类型加双冒号::的方式来调用。
Rust中Self(大写S)和self(小写s)都是关键字,大写S的是类型名,小写s的是变量名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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()); }
|
另一种写法“内在方法”
1 2 3
| impl Circle { fn get_radius(&self) -> f64 { self.radius } }
|
trait中可以包含方法的默认实现。如果这个方法在trait中已经有了方法体,那么在针对具体类型实现的时候,就可以选择不用重写。
self参数甚至可以是Box指针类型self
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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和impl trait的时候,Rust规定了一个Coherence Rule(一致性规则)或称为Orphan Rule(孤儿规则):impl块要么与trait的声明在同一个的crate中,要么与类型的声明在同一个crate中。
静态方法
没有receiver参数的方法(第一个参数不是self参数的方法)。通过Type::FunctionName()的方式调用。
1 2 3 4 5 6 7 8 9 10 11 12
| struct T(i32); impl T { // 这是一个静态方法 fn func(this: &Self) { println!{"value {}", this.0}; } } fn main() { let x = T(42); // x.func(); 小数点方式调用是不合法的 T::func(&x); }
|
trait中也可以定义静态函数
1 2 3 4 5 6 7 8 9 10
| pub trait Default { fn default() -> Self; }
// 这里用到了“泛型”,请参阅第21章 impl<T> Default for Vec<T> { fn default() -> Vec<T> { Vec::new() } }
|
这样的写法是错误的。请一定要记住,trait的大小在编译阶段是不固定的。
1 2 3
| let x: Shape = Circle::new(); // Shape 不能做局部变量的类型 fn use_shape(arg : Shape) {} // Shape 不能直接做参数的类型 fn ret_shape() -> Shape {} // Shape 不能直接做返回值的类型
|
目前版本的Rust规定,在函数参数传递、返回值传递等地方,都要求这个类型在编译阶段有确定的大小。
完整函数调用语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| trait Cook { fn start(&self); } trait Wash { fn start(&self); } struct Chef; impl Cook for Chef { fn start(&self) { println!("Cook::start"); } } impl Wash for Chef { fn start(&self) { println!("Wash::start"); } }
fn main() { let me = Chef; me.start();// 出现歧义
}
fn main() {//无歧义 let me = Chef; // 函数名字使用更完整的path来指定,同时,self参数需要显式传递 <Cook>::start(&me); <Chef as Wash>::start(&me); }
|
trait约束和继承
1 2 3 4 5 6 7 8 9 10
| use std::fmt::Debug; fn my_print<T : Debug>(x: T) { println!("The value is {:? }.", x); } fn main() { my_print("China"); my_print(41_i32); my_print(true); my_print(['a', 'b', 'c']) }
|
冒号后面加trait名字,就是这个泛型参数的约束条件。它要求这个T类型实现Debug这个trait。这是因为我们在函数体内,用到了println!格式化打印,而且用了{:? }这样的格式控制符,它要求类型满足Debug的约束,否则编译不过。
另外写法
1 2 3
| fn my_print<T>(x: T) where T: Debug { println!("The value is {:? }.", x); }
|
trait继承
1 2 3 4 5 6 7
| trait Base {} trait Derived : Base {} struct T; impl Derived for T {} impl Base for T {} //必须实现Base和Derived,必须两个都实现,不然编译报错 fn main() { }
|
Derive
1 2 3 4 5 6 7 8 9
| #[derive(Copy, Clone, Default, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]//derive称为attribute,等于实现了列表中所有的trait struct Foo { data : i32 } fn main() { let v1 = Foo { data : 0 }; let v2 = v1; println!("{:? }", v2); }
|
支持的derive有如下这些
1 2
| Debug Clone Copy Hash RustcEncodable RustcDecodable PartialEq Eq ParialOrd Ord Default FromPrimitive Send Sync
|
标准库中常见的trait简介
Display和Debug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| // std::fmt::Display pub trait Display { fn fmt(&self, f: &mut Formatter) -> Result<(), Error>; } // std::fmt::Debug pub trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<(), Error>; }
use std::fmt::{Display, Formatter, Error}; #[derive(Debug)] struct T { field1: i32, field2: i32, } impl Display for T { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { write! (f, "{{ field1:{}, field2:{} }}", self.field1, self.field2) } } fn main() { let var = T { field1: 1, field2: 2 }; println!("{}", var); //实现了Display trait println!("{:? }", var);//实现了Debug trait,紧凑模式,打印struct println!("{:#? }", var);//实现了Debug trait,美化模式,打印struct有换行 }
|
❏ Display假定了这个类型可以用utf-8格式的字符串表示,它是准备给最终用户看的,并不是所有类型都应该或者能够实现这个trait。这个trait的fmt应该如何格式化字符串,完全取决于程序员自己,编译器不提供自动derive的功能。
❏ 标准库中还有一个常用trait叫作std::string::ToString,对于所有实现了Display trait的类型,都自动实现了这个ToString trait。它包含了一个方法to_string(&self) -> String。任何一个实现了Display trait的类型,我们都可以对它调用to_string()方法格式化出一个字符串。
❏ Debug则是主要为了调试使用,建议所有的作为API的“公开”类型都应该实现这个trait,以方便调试。它打印出来的字符串不是以“美观易读”为标准,编译器提供了自动derive的功能。
PartialOrd / Ord / PartialEq / Eq
❏ 如果a < b则一定有! (a > b);反之,若a > b,则一定有!(a < b),称为反对称性。
❏ 如果a < b且b < c则a < c,称为传递性。
❏ 对于X中的所有元素,都存在a < b或a > b或者a == b,三者必居其一,称为完全性。
满足其中两个称为偏序,全满足为全序。
**浮点数为偏序,因为NaN的存在。**导致浮点数无法排序
Rust设计了两个trait来描述偏序和全序。
1 2 3 4 5 6 7 8 9 10 11
| pub trait PartialOrd<Rhs: ? Sized = Self>: PartialEq<Rhs> { fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>; //可能返回空或者Ordering fn lt(&self, other: &Rhs) -> bool { //... } fn le(&self, other: &Rhs) -> bool { //... } fn gt(&self, other: &Rhs) -> bool { //... } fn ge(&self, other: &Rhs) -> bool { //... } }
pub trait Ord: Eq + PartialOrd<Self> { fn cmp(&self, other: &Self) -> Ordering;//返回Ordering }
|
f32和f64类型都只实现了PartialOrd,而没有实现Ord。
Sized
这个trait定义在std::marker模块中,它没有任何的成员方法。它有#[lang =”sized”]属性,说明它与普通trait不同,编译器对它有特殊的处理。用户也不能针对自己的类型impl这个trait。一个类型是否满足Sized约束是完全由编译器推导的,用户无权指定。
在Rust中,但凡编译阶段能确定大小的类型,都满足Sized约束。
Rust中对于动态大小类型专门有一个名词Dynamic Sized Type。我们后面将会看到的[T], str以及dyn Trait都是DST。
Default
对于那种无参数、无错误处理的简单情况,标准库中提供了Default trait来做这个统一抽象。
1 2 3
| trait Default { fn default() -> Self; }
|
总结
本章对trait这个概念做了基本的介绍。除了上面介绍的之外,trait还有许多用处:
❏ trait本身可以携带泛型参数;
❏ trait可以用在泛型参数的约束中;
❏ trait可以为一组类型impl,也可以单独为某一个具体类型impl,而且它们可以同时存在;
❏ trait可以为某个trait impl,而不是为某个具体类型impl;
❏ trait可以包含关联类型,而且还可以包含类型构造器,实现高阶类型的某些功能;
❏ trait可以实现泛型代码的静态分派,也可以通过trait object实现动态分派;
❏ trait可以不包含任何方法,用于给类型做标签(marker),以此来描述类型的一些重要特性;
❏ trait可以包含常量。trait这个概念在Rust语言中扮演了非常重要的角色,承担了各种各样的功能,在写代码的时候会经常用到。
本章还远没有把trait相关的知识讲解完整,更多关于trait的内容,请参阅本书后文中与泛型、trait object线程安全有关的章节。