Rust Trait

Rust trait

Rust中的trait类似于其他语言的接口类型

一般定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait A {
fn a(&self) -> i32; // 无默认实现
fn b(&self) -> i32 {
1
} // 有默认实现
}

struct B;

impl A for B {
fn a(&self) -> i32 {
1
}
// 有默认实现可以复用
}

常见的两张分发形式

  1. 静态分发
    1
    2
    3
    4
    5
    6
    7
    fn test(a: impl A) -> i32 {
    a.a()
    }
    // 等价于 =>
    fn test<T: A>(a: T) -> i32 {
    a.a()
    }

    静态分发没有性能损耗,会在编译期间进行单态化,在栈上分配,就像是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct C;
impl A for C {
fn a(&self) -> i32 {
1
}
// 有默认实现可以复用
}

fn test(a: impl A) -> i32 {
a.a()
}
// 单态化后 会新生成两个函数 =>

fn test_B(a: B) -> i32 {
a.a()
}

fn test_C(a: C) -> i32 {
a.a()
}
  1. 动态分发
    1
    2
    3
    fn test(a: &dyn A) -> i32 {
    a.a()
    }

    动态分发有性能损耗,实在运行时才确定,有延迟绑定的效果,灵活性高,在堆上分配,原有类型会被抹去,创建一个trait object,并为其分配满足该trait的vtable

从上图可以看出,同一种trait object只会创建一次,被重复引用

在参数中使用trait object

在参数中使用trait object比较简单,就像这样:

1
2
3
4
5
6
trait CookieStore: Send + Sync { // : Send + Sync表示实现CookieStore trait时也要满足Send和Sync trait,类似于继承
fn set_cookies(
&self,
cookie_headers: &mut dyn Iterator<Item = &HeaderValue>,
url: &Url);
}

在函数返回值中使用trait object

这是trait object使用频率比较高的场景

1
2
3
trait Storage: Send + Sync + 'static {
fn get_iter(&self, table: &str) -> Result<Box<dyn Iterator<Item = Kvpair>>, KvError>;
}

async trait的例子

rust目前并不支持trait中使用async fn,但可以使用async_trait宏来解决

1
2
3
4
5
6
7
8
9
10
11
12
#[async_trait]
trait Fetch {
type Error;
async fn fetch(&self) -> Result<String, Self::Error>;
}

// 宏展开后 =>

trait Fetch {
type Error;
fn fetch<'a>(&'a self) -> Result<Pin<Box<dyn Future<Output = String> + Send + 'a>>, Self::Error>;
}

实际上就是使用了trait object作为返回值,这样就可以不管fetch()的实现和Future的类型,都可以被 trait object 统一起来,调用者只需要按照正常 Future 的接口使用即可.

在数据结构中使用trait object

1
2
3
4
5
6
7
8
struct State {
rng: Box<dyn Random>
}

// 当然也可以使用泛型
struct State<R> where R: Random {
rng: Box<R>
}

这是我们大部分时候处理这样的数据结构的选择。但是,过多的泛型参数会带来两个问题:首先,代码实现过程中,所有涉及的接口都变得非常臃肿,你在使用 HandshakeState<R, D, K> 的任何地方,都必须带着这几个泛型参数以及它们的约束。其次,这些参数所有被使用到的情况,组合起来,会生成大量的代码。而使用 trait object,我们在牺牲一点性能的前提下,消除了这些泛型参数,实现的代码更干净清爽,且代码只会有一份实现。

闭包

1
2
3
struct AttributeGetter(
Arc<dyn Fn(&Instance, &mut Host) -> crate::Result<PolarValue> + Send + Sync>,
);