Trait
Trait有scope的概念, 必须声明可见后才能使用. 这样可以避免名称冲突, 同时也可以灵活地自定义实现Trait
类似C/C++的虚基类接口定义, 但是最明显的差别是:
- C/C++的虚基类继承会要求在子类每个实例的内存模型中生成虚函数表, 占用对象内存.
- 而为一个struct实现一个Trait对struct实例本身没有任何内存开销.
Trait本身并没有固定的大小(只有引用某个实现该Trait类型的实例后才有占用大小一说), 因此不能直接声明和使用Trait类型的变量. 对某个实现该Trait类型的实例的有效引用称为
Trait Object
1
2
3
4
5use std::io::Write;
let mut buf: Vec<u8> = vec![];
let writter: Wirte = buf; // error: `Write` does not have a constant size
let writter: &mut Write = &mut buf; // Ok, writter is a Trait ObjectTrait Object
实际是一个fat pointer
, 占用两个机器字字节, 一个指向实际的实例对象, 一个指向虚基表vtable
. 注意, 这个vtable
本身不属于实例, 而是当作为一个Trait Object
进行引用时附加给这个引用的fat pointer
, 内存布局如下:默认方法(Default method), 可以在trait的声明中定义默认方法.
1
2
3
4
5
6
7
8
9trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
let mut bytes_written = 0;
while bytes_written < buf.len() {
bytes_written += self.write(&buf[bytes_written...])?;
}
Ok(())
}扩展trait(extension trait), 可以为其它trait类型扩展出自定义trait的接口.
1
2
3
4
5
6
7
8
9
10trait WriteHtml {
fn write_html(&mut self, html: &HtmlDocument) -> io::Result<()>;
}
// For every type W that implements Write, here's an implementation for WriteHtml for W
impl<W: Write> WriteHtml for W {
fn write_html(&mut self, html: &HtmlDocument) -> io::Result<()> {
...
}
}Self
关键字(notself
), 用来作为别名, 声明为实现该Trait的具体类型.1
2
3pub trait Clone {
fn clone(&self) -> Self;
}Subtrait, 类似接口继承. 可以这么理解: 凡是实现了Subtrait(
Creature
)的类型, 也必须实现父Trait(Visible
)的所有方法., 有的地方又叫Extend trait
.1
2
3
4// Every type that implements Creature must also implement the Visible trait.
trait Creature: Visible {
fn ...;
}Static Methods, 类似接口的静态方法, 没有
self
参数, 通常用作工厂模式.1
2
3
4trait StringSet {
fn new() -> Self;
fn from_slice(strings: &[&str]) -> Self;
}调用方式是实现该trait的类型名称加上
::
, 如果是泛型(下面会介绍), 则用泛型变量.1
2
3
4
5
6
7// SortedStringSet impl the StringSet trait
let set1 = SortedStringSet::new();
fn func<S: StringSet>() -> S {
let mut set2 = S::new();
...
}
Generic
泛型Generic用
<>
来表示, 通常用全大写变量表示泛型参数.1
2fn hello(out: &mut Write){}
fn hello<W: Write>(out: &mut W){}编译时Rust会为使用泛型的每个类型生成一套机器代码, 没有运行时开销, 但是会生成额外的机器码, 增加二进制大小.
可以使用泛型的场景:
- 函数
- enum、struct及其method
- 类型别名(
type aliases
):type PancakeResult<T> = Result<T, PancakeError>;
- 函数的
where
语句
Difference between Trait & Generic
Generic
没有运行时开销, 速度更快, 而Trait
需要运行时才能知道实际类型(也就是执行时根据Trait Object来查找vtable
方法). 如果需要处理大量各种不同类型对象的特性且对二进制大小有要求, 可以用Trait. 否则建议使用Generic.Not every trait can support trait objects. 并不是所有的Trait都支持
Trait Object
方式使用. 包含静态方法的Trait, 由于没有self参数, 在泛型函数中其静态方法不能通过Trait Object
来调用. 例如下面(仍然以上面静态方法代码为例):1
2
3
4
5
6
7
8
9
10
11// Return the set of words in `document` that aren't in `wordlist`.
fn unknown_words<S: StringSet>(document: &Vec<String>, wordlist: &S) -> S {
// 不能通过Trait Object wordlist 调用new, 只能通过泛型变量S::
let mut unknowns = S::new();
for word in document {
if !wordlist.contains(word) {
unknowns.add(word);
}
}
unknowns
}