Rust特性(Trait)

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

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// trait声明
trait Shape {
    fn area1(self);             //method1(self: Self); 
    fn area(&self);            //area(self: &Self); 
    fn larger(&mut self) -> f64;        
}

struct Circle {
    r: f64,
}

// trait 实现
impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.r * self.r
    }
}

// 匿名trait
impl Circle {
    fn get_radius(&self) -> f64 {
        self.r
    }
}

trait 泛型

1
2
3
4
5
6
7
// where 从句
fn foo<T, K>(x: T, y: K) 
    where T: Clone, K: Clone + Debug {
    x.clone();
    y.clone();
    println!("{:?}", y);
}

特征对象(trait object)

  • 指向trait的指针就是Trait Object,比如 &SomeTrait 和 Box<SomeTrait>;

  • &SomeTrait 类型和普通的指针类型不同, 不仅包括指向真实对象的指针,还包括一个指向虚函数表的指针;

  • rust通过TraitObject用来实现动态分发;

  • Trait Object是一个胖指针fat pointer, 占用两个机器字字节, 一个指向实际的实例对象, 一个指向虚基表vtable

1
2
3
4
5
//std::raw
pub struct TraitObject {
    pub data: *mut (),
    pub vtable: *mut (),
}

Supertrait

关联类型

关联类型是一个将类型占位符与trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型。

  • 关联类型类似泛型,不同之处关联类型,无需标注类型,无须多次实现trait;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pub trait Iterator {
    type Item;  //关联类型
    fn next(&mut self) -> Option<Self::Item>;
}
impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
    }
}

常用Trait

Default

Default trait是针对无参构造函数的抽象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//std::default::Default
pub trait Default {
    fn default() -> Self;
}
// Vec default
impl<T> Default for Vec<T> {
    fn default() -> Vec<T> {
        Vec::new()
    }
}

Derive

Rust提供了一个特殊属性,以自动impl某些trait,编译阶段会自动展开为相应的impl块:

1
2
3
4
#[derive(Copy, Clone, Default)]
struct Foo {
    data: i32,
}

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()格式化字符串;

1
2
3
4
5
6
7
8
//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>;
}

派生(derive)

通过 #[derive] 属性,编译器能够提供某些 trait 的基本实现。如果 需要更复杂的行为,这些 trait 也可以手动实现。

下面是可以自动派生的 trait:

  • 比较 trait: EqPartialEqOrdPartialOrd
  • Clone, 用来从 &T 创建副本 T
  • Copy,使类型具有 “复制语义”(copy semantics)而非 “移动语义”(move semantics)。
  • Hash,从 &T 计算哈希值(hash)。
  • Default, 创建数据类型的一个空实例。
  • Debug,使用 {:?} formatter 来格式化一个值。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#[derive(PartialEq, PartialOrd)]
struct Centimeters(f64);

// `Inches`,可以打印的元组结构体
#[derive(Debug)]
struct Inches(i32);

impl Inches {
    fn to_centimeters(&self) -> Centimeters {
        let &Inches(inches) = self;

        Centimeters(inches as f64 * 2.54)
    }
}

Ord/PartialOrd/Eq/PartialEq

标记特性(Marker Trait)

  • marker traitstd::marker模块中定义;

  • Marker Trait不包含任何方法;

  • Marker Trait主要用于编译器,标记marker trait的类型,其相应语义在编译期得到保证;

Send和Sync

SyncSend主要用于线程间数据同步作用的;

Send

  • Send是一个marker trait

  • Send允许在线程间转移所有权;

  • 标记为Send类型的数据能安全地被move到另一个线程;

  • 几乎所有的 Rust 类型都是Send 的, 裸指针除外;

  • 任何完全由 Send 的类型组成的类型也会自动被标记为 Send

  • Rc<T>没有实现send trait,无法用于线程共享,需用Arc<T>

Sync

  • Sync是一个marker trait;

  • Sync允许在线程间共享所有权;

  • 标记为Sync的类型可以在线程间安全的共享(通过引用);

  • 基本类型是 Sync 的;

  • 完全由 Sync 的类型组成的类型也是 Sync 的;

  • 若类型 T 的引用&TSend,则TSync

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. 大小取决于泛型的参数类型:

1
2
3
4
5
6
#[lang = "sized"]
pub trait Sized {
    // Empty.
}

struct Bar<T: ?Sized>(T);  // 编译期可确定大小类型和动态大小类型两种类型

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;

  • CloneSized的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 trait

  • clone方法不能失败(infallible), 对于std::fs::File这样的类型, 有try_clone方法, 返回std::io::Result<File>

1
2
3
4
5
6
trait Clone: Sized {
  fn clone(&self) -> Self;
  fn clone_from(&mut self, source: &Self) {
    *self = source.clone()
  }
}

From/Into

  • From: 对于类型为 U 的对象 foo,如果它实现了 From<T>,那么,可以通过 let foo = U::from(bar) 来生成自己

1
2
3
4
5
6
trait Into<T>: Sized {
  fn into(self) -> T;
}
trait From<T>: Sized {
  fn from(T) -> Self;  // 静态方法
}
1
2
3
4
5
6
7
8
9
// as_ref和Borrow的区别 ?

// as_ref 是转引用函数, 将具有所有权对象转换成引用对象,
// 不改变被转换对象的基础上产生一个引用对象.

// as_ref 并不是所有类型都默认支持, 很多时候都需要自己去声明.
// as_ref 是AsRef trait 的公共接口方法.
// 只有那些实现了 as_ref 公共接口方法的类型才能使用as_ref.
// 目前: Option, Box, Result 这三种类型默认提供支持as_ref.

参考

  1. https://wiki.jikexueyuan.com/project/rust-primer/

  2. https://unpluggedcoder.me/2019/08/31/Rust%E5%85%A5%E9%97%A8%E5%A4%B1%E8%B4%A5%E4%B9%8BTraits&Generics/

  3. https://zhuanlan.zhihu.com/p/21730929

  4. https://unpluggedcoder.me/2019/09/01/Rust%E5%85%A5%E9%97%A8%E5%A4%B1%E8%B4%A5%E4%B9%8BUtility%20Traits/

  5. https://wiki.jikexueyuan.com/project/rust-primer/trait/trait-object.html

  6. https://zhuanlan.zhihu.com/p/23791817

updatedupdated2024-05-102024-05-10