Rust特征对象(Trait Object)
创始人
2025-05-28 03:38:15
0

特征对象(Trait Object)

前面学习的泛型,特征。它们都只能实现静态多态。它们和类型的绑定发生在编译期。如何让其实现C++中“父类指针指向子类对象”,从而实现运行时的多态。为了解决这个问题,Rust引入了——特征对象。

特征对象定义

我们直接来看一段代码。它展示了特征对象的两种定义方式。

// 定义特征Draw
trait Draw {fn draw(&self) -> String;
}// 为u8类型实现Draw特征
impl Draw for u8 {fn draw(&self) -> String {format!("u8: {}", *self)}
}
// 为f64类型实现Draw特征
impl Draw for f64 {fn draw(&self) -> String {format!("f64: {}", *self)}
}// 若 T 实现了 Draw 特征, 则调用该函数时传入的 Box 可以被隐式转换成函数参数签名中的 Box
fn draw1(x: Box) {// 由于实现了 Deref 特征,Box 智能指针会自动解引用为它所包裹的值,然后调用该值对应的类型上定义的 `draw` 方法println!("{}", x.draw());}fn draw2(x: &dyn Draw) {println!("{}", x.draw());
}fn main() {let x = 1.1f64;let y = 8u8;//可以通过 & 引用或者 Box 智能指针的方式来创建特征对象,下面分别通过两种方式来展示创建的特征对象。// x 和 y 的类型 T 都实现了 `Draw` 特征,因为 Box 可以在函数调用时隐式地被转换为特征对象 Box // 基于 x 的值创建一个 Box 类型的智能指针,指针指向的数据被放置在了堆上println!("通过Box创建的特征对象");draw1(Box::new(x));// 基于 y 的值创建一个 Box 类型的智能指针draw1(Box::new(y));println!("通过引用创建的特征对象:");draw2(&x);draw2(&y);
}

这段代码中我们首先定义了特征Draw,然后为u8和f64类型实现了特征Draw。我们的重点是Box::newdyn

  1. draw1 函数的参数是 Box 形式的特征对象,该特征对象是通过 Box::new(x) 的方式创建的
  2. draw2 函数的参数是 &dyn Draw 形式的特征对象,该特征对象是通过 &x 的方式创建的
  3. dyn 关键字只用在特征对象的类型声明上,在创建时无需使用 dyn

有了特征对象,就实现了鸭子类型。我们可以在一个Vector中存放特征对象,从而实现不同类型的存储的容器。注意&dynBox都是在编译期已知大小的。关于特征对象的动态分发请看这里。如果还是没明白动态绑定,还可以看一下的参考资料。

  1. https://blog.csdn.net/zy010101/article/details/105283049
  2. https://blog.csdn.net/zy010101/article/details/105297070
  3. https://blog.csdn.net/zy010101/article/details/105276523

特征对象的限制

只有对象安全(object-safe)的 trait 可以实现为特征对象。这里有一些复杂的规则来实现 trait 的对象安全,但在实践中,只有两个相关的规则。如果一个 trait 中定义的所有方法都符合以下规则,则该 trait 是对象安全的:

  1. 返回值不是 Self
  2. 没有泛型类型的参数

Self 关键字是我们在 trait 与方法上的实现的别称,trait 对象必须是对象安全的,因为一旦使用 trait 对象,Rust 将不再知晓该实现的返回类型。如果一个 trait 的方法返回了一个 Self 类型,但是该 trait 对象忘记了 Self 的确切类型,那么该方法将不能使用原本的类型。当 trait 使用具体类型填充的泛型类型时也一样:具体类型成为实现 trait 的对象的一部分,当使用 trait 对象时,会忘了类型是什么,无法知道应该用什么类型来填充泛型类。特征对象会丢失掉原来的类型。例如:

// 若 T 实现了 Draw 特征, 则调用该函数时传入的 Box 可以被隐式转换成函数参数签名中的 Box
fn draw1(x: Box) {// 由于实现了 Deref 特征,Box 智能指针会自动解引用为它所包裹的值,然后调用该值对应的类型上定义的 `draw` 方法println!("{}", x.draw());// x.clone();   // 错误
}

上述代码中的x是特征对象,它会丢失掉原来的类型,它无法调用u8类型或者f64类型所拥有的方法和特征,因为x现在只知道自己是Draw的特征对象。

一个非对象安全的 trait 例子是标准库中的 Clone trait。Clone trait 中的 clone 方法的声明如下:

pub trait Clone {fn clone(&self) -> Self;
}

String 类型实现了 Clone trait,当我们在 String 的实例对象上调用 clone 方法时,我们会得到一个 String 类型实例对象。相似地,如果我们调用 Vec 实例对象上的 clone 方法,我们会得到一个 Vec 类型的实例对象。clone 方法的标签需要知道哪个类型是 Self 类型,因为 Self 是它的返回类型

当我们尝试编译一些违反 trait 对象的对象安全规则的代码时,我们会收到编译器的提示。例如,我们想实现函数参数接受一个 Clone 特征对象。

fn te(x: Box) {println!("Clone");
}

变异含有该函数的代码时,会发生如下错误。

error[E0038]: the trait `Clone` cannot be made into an object
--> src/main.rs:31:14
|
31 | fn te(x: Box) {
|              ^^^^^^^^^ `Clone` cannot be made into an object
|
= note: the trait cannot be made into an object because it requires `Self: Sized`
= note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit For more information about this error, try `rustc --explain E0038`.
error: could not compile `trait_object` due to previous error

相关内容

热门资讯

王毅:日本现职领导人讲了不该讲... 据外交部网站,11月19日至22日,中共中央政治局委员、外交部长王毅应邀赴吉尔吉斯斯坦、乌兹别克斯坦...
陕西兴平失联的12岁双胞胎姐妹... 据大风新闻,11月21日晚7时17分,陕西兴平一对12岁的双胞胎姐妹从家里外出。走的时候还穿着蓝色校...
原创 中... 中国再下一令,日本却辗转从韩国口中才得知这个噩耗,高市有点坐不住了,关键时刻放出猛料,要把美国拉下水...
公园掰手腕致骨折起诉索赔被驳回 公园内一场普通的掰手腕,竟导致手臂骨折、花费数万元医疗费。近日,江苏省苏州工业园区人民法院审理了一起...
护航浙商出海发展 浙江省涉外法... 中新网杭州11月23日电 (钱晨菲 吴怡欣)11月23日,浙江省涉外法律服务合作对接会在杭州举行,浙...
吕文君社媒庆祝夺冠:不只是海港... 2025赛季中超联赛于昨日正式落幕,上海海港队凭借出色的表现,成功捧起了冠军奖杯,成为了中超历史上又...
日本政府顾问:无需等到160关... 日本政府顾问表示,高市早苗政府对日元干预将采取更积极姿态,以抑制日元疲软带来的通胀压力,干预门槛可能...
被摄影师起诉侵权 视觉中国公开... 来源:每日经济新闻 持续两年多的摄影师起诉视觉中国(000681.SZ)侵权一案近日迎来进展。 法...
《哪吒2》被质疑过多使用动捕技... 搜狐娱乐讯 22日,奥斯卡公开的最佳动画长片奖“符合参评资格”大名单中没有《哪吒之魔童闹海》,引发热...
为了少付合同款,湖南一公司诉讼... 华声在线11月23日讯(文/视频 全媒体记者 杨昱 通讯员 胡云淞)为了少支付40万元合同款及利息,...