Rust特性(Trait)
简介
Trait(特性) 是一组方法的集合,实现trait的类型可以访问该 trait 中定义的其他方法。任何类型都可以实现 trait;
trait 是一种约束,而不是具体类型,它属于DST(类似于
str),其 size 无法在编译阶段确定,只能通过指针来间接访问;
要点
trait必须声明可见后才能使用;trait本身并没有固定的大小,不能直接声明和使用trait类型的变量;某个实现该
trait类型的实例的有效引用称为Trait Object;所有trait都有一个隐藏的类型
Self,代表当前实现此trait的具体类型;函数第一个参数是
self且为Self相关类型(Self, &Self, &mut Self, Box<Self>),则函数为方法(method),self称为receiver;没有receiver参数的函数为静态方法,可通过
Type::Function()方式调用;匿名trait无须名字,可直接在impl中实现;
可以在trait的声明中定义默认方法;
扩展trait(extension trait), 可以为其它trait类型扩展出自定义trait的接口,impl块必须与trait或struct声明在同一个crate中(孤儿规则);继承:凡是实现了Subtrait(
Creature)的类型, 也必须实现父Trait(Visible)的所有方法;
孤儿规则(orphan rule)
- 如果要实现外部定义的
trait需要先将其导入作用域; - 不允许对外部类型实现外部
trait; - 可以对外部类型实现自定义的
trait; - 可以对自定义类型上实现外部
trait;
示例
| |
trait 泛型
| |
特征对象(trait object)
指向
trait的指针就是Trait Object,比如&SomeTrait和Box<SomeTrait>;&SomeTrait类型和普通的指针类型不同, 不仅包括指向真实对象的指针,还包括一个指向虚函数表的指针;rust通过
TraitObject用来实现动态分发;Trait Object是一个胖指针fat pointer, 占用两个机器字字节, 一个指向实际的实例对象, 一个指向虚基表vtable;
| |
Supertrait
关联类型
关联类型是一个将类型占位符与trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型。
关联类型类似泛型,不同之处关联类型,无需标注类型,无须多次实现trait;
| |
常用Trait
Default
Default trait是针对无参构造函数的抽象
| |
Derive
Rust提供了一个特殊属性,以自动impl某些trait,编译阶段会自动展开为相应的impl块:
| |
Rust支持自动derive的trait有:
Debug
Clone/Copy
Hash
PartialEq/Eq/PartialOrd/Ord
Send/Sync
Default
FromPrimitive
RustcEncodable/RustcDecodable
Display/Debug
实现
Display特性的类型,可用{}格式控制打印;实现
Debug特性的类型,可用{:?},{:#?}格式控制打印;Display一般给最终用户显示的,通常用utf-8格式字符输出;Debug特性主要用于调试,一般为byte字符,编译器提供自动derive功能;实现
Dispaly特性的类型都自动实现了ToString特性,可直接通过to_string()格式化字符串;
| |
派生(derive)
通过 #[derive] 属性,编译器能够提供某些 trait 的基本实现。如果 需要更复杂的行为,这些 trait 也可以手动实现。
下面是可以自动派生的 trait:
- 比较 trait:
Eq,PartialEq,Ord,PartialOrd Clone, 用来从&T创建副本T。Copy,使类型具有 “复制语义”(copy semantics)而非 “移动语义”(move semantics)。Hash,从&T计算哈希值(hash)。Default, 创建数据类型的一个空实例。Debug,使用{:?}formatter 来格式化一个值。
| |
Ord/PartialOrd/Eq/PartialEq
标记特性(Marker Trait)
marker trait在std::marker模块中定义;Marker Trait不包含任何方法;Marker Trait主要用于编译器,标记marker trait的类型,其相应语义在编译期得到保证;
Send和Sync
Sync 和 Send主要用于线程间数据同步作用的;
Send
Send是一个marker trait;Send允许在线程间转移所有权;标记为
Send类型的数据能安全地被move到另一个线程;几乎所有的 Rust 类型都是
Send的, 裸指针除外;任何完全由
Send的类型组成的类型也会自动被标记为Send;Rc<T>没有实现sendtrait,无法用于线程共享,需用Arc<T>;
Sync
Sync是一个marker trait;Sync允许在线程间共享所有权;标记为
Sync的类型可以在线程间安全的共享(通过引用);基本类型是
Sync的;完全由
Sync的类型组成的类型也是Sync的;若类型 T 的引用
&T是Send,则T是Sync;
Sized
Sized是一种marker trait,无任何方法和联合类型,无法实现;Sized标记在编译期可确定大小的类型;Unsized标记的是动态大小类型,在编译期无法确定其大小;Rust不能在变量里保存大小未确定(
unsized)的值, 也不能把unsize值作为参数.所有大小确定的类型都实现了
std::marker::Sizedtrait;Sized trait只能用于参数的类型声明(及检查), 例如
T: Sized不能用于其它用途;?Sized为不定大小类型 questionably sized, 可以是确定大小, 也可以是非确定大小类型.struct的最后一个字段可以是
?Sized类型, 但如果这样, struct本身就变为了unsized.但如果写成泛型, 并传入一个Sized类型, 那么这个类型的struct仍然是Sized. 大小取决于泛型的参数类型:
| |
Copy/Clone
Copy
Copy是一个
marker trait,定义为std::marker::Copy;标记为
Copy的类型可以通过内存copy实现该类型的副本;标记为
Copy的类型,在变量绑定、函数参数传递、函数返回值传递等场景下,不是move语义,是copy语义;只有所有的成员都实现了
Copy,这个类型才有资格实现 Copy trait;常见的数字类型、bool类型、共享借用指针&,都是具有
Copy属性的类型;Box、Vec、&mut 等类型不具备 Copy 属性的类型;
数组,元组,struct,enum这些组合类型当其每一个成员都是Copy类型是,其自身自动Copy;
Clone
Clonetrait不是marker trait;Clone是Sized的sub-trait, 所以Self类型必须是Sized;clone方法必须返回和self独立无关的一份拷贝;如果所有的字段都实现了Clone, 那么
struct可以加上属性:#[derive(Clone)]自动实现Clone trait;
通常情况下clone的成本比较高, 但是对于
Rc<T>和Arc<T>这类的类型, Rust的对它们的clone只是简单的增加计数.通常尽可能使用
clone_from来减少clone开销, 这会允许一些优化. 例如, String的clone, 被赋值的String如果capacity够大, 可以不需要释放内存, 直接把源的内容拷贝过来.如果所有的字段都实现了Clone, 那么
struct可以加上属性:#[derive(Clone)]自动实现Clone traitclone方法不能失败(infallible), 对于std::fs::File这样的类型, 有try_clone方法, 返回std::io::Result<File>
| |
From/Into
From: 对于类型为U的对象foo,如果它实现了From<T>,那么,可以通过let foo = U::from(bar)来生成自己
| |
| |