Rust 中级教程 第18课——trait object (1)本篇文章是 `trait` 篇的知识,由于前面一直没有介
0x00 开篇
本篇文章是 trait
篇的知识,由于前面一直没有介绍引用的相关知识,所以推迟到这里介绍了。在 Rust 除了泛型可以编写多态化的代码外,trait object
也可以做到。本篇文章的阅读时间大约 5 分钟。
0x01 什么是 trait object
在 Rust 中,我们在使用 trait
的时候,并不能直接声明 trait
类型的变量,比如(下面的代码是错误的):
// 以下代码省略...请参阅源码
// 定义一个Shape trait
// 定义两个struct Rectangle和Circle 并实现 Shape
// 以上代码省略...请参阅源码
let circle = Circle::new(10.0);
// 下面代码是错误的
let shape: Shape = circle;
// 上面代码是错误的
如果你对 Java
比较熟悉,那么你会认为上面的代码应该是正确的。由于在Java中,所有的变量都是对象的引用,而不是对象本身。这意味着当你创建一个对象时,实际上是在堆中为该对象分配空间,并在栈上创建一个变量来存储该对象的引用。当你使用该变量来访问对象时,实际上是通过该引用来访问堆中的对象。那么其实 Rust 也是同样类似的,只不过在 Java 中没有指针的概念,所有引用都是隐式的。在 Rust 中,引用都是显示的。所以我们应该稍微改变下写法:
let shape: Shape = &circle; // 这种写法依然错误
上面的转换形式还是不够,在 Rust 中,trait 定义了一组行为或者方法,它不是一个实际的数据类型,所以它是没有固定大小的。
那么所以我们将 &circle
指向一个 trait
的引用 &Shape
。转为下面的写法:
// 下面这种写法在 rust 2015 和 2018 版本是正确的,且可以正常编译,但是在 rust 2021版本会提示编译错误,下面会提到
let shape: &Shape = &circle;
所以像这样,指向一个 trait 类型的引用,将其称为 trait object。我一般将 trait object
翻译为特型对象或者习惯不翻译。
现在我们已经了解 trait object
的定义了。
在 Rust 2021版(应该是从 Rust 1.56版本开始)中,trait object 使用时必须包含 dyn
关键字(看下图)。
官方文档地址:doc.rust-lang.org/error_codes…
所以正确的写法应该是:
let shape: &dyn Shape = &circle;
// 如果是可变,需要添加mut
let shape: &mut dyn Shape = &mut circle;
给出的原因是:由于 trait object 只有在运行时阶段才能确定类型。如果我们忽略了 dyn
(看下面的代码),我们很难分辨出 Shape
是一个 trait object。为了解决这个问题,我们添加 dyn
关键字修饰,关键字 dyn
用于表示动态类型,告诉编译器在运行时才能确定对象的类型。
let rectangle = Rectangle::new(3.0, 4.0);
// 省略 dyn 错误范例
let shape: &Shape = &rectangle;
let shape: &dyn Shape = &rectangle;
// 结构体的引用
let rec_ref: &Rectangle = &rectangle;
0x02 impl trait 和 trait object 的区别
看到这里,有没有感觉到跟之前的 impl trait 有点像呢?简单讨论下他们之间的区别。
- impl trait 无法用于变量绑定,只允许在函数或者方法中作为返回值或者参数值使用。
// 以下代码是错误的
let shape: impl Shape = circle;
下面的代码是正确的:
fn create_circle_impl_trait() -> impl Shape {
let circle = Circle::new(5.0);
return circle;
}
fn print_shape(shape: impl Shape) {
println!("[print_shape] area = {}, perimeter = {}", shape.area(), shape.perimeter());
}
fn main() {
let circle_impl = create_circle_impl_trait();
println!("[circle_impl] area = {}, perimeter = {}", circle_impl.area(), circle_impl.perimeter());
print_shape(circle_impl);
}
// 运行结果
// [circle_impl] area = 78.53981633974483, perimeter = 31.41592653589793
// [print_shape] area = 78.53981633974483, perimeter = 31.41592653589793
trait object
是一种动态的类型,它允许在运行时将对象与 trait 关联。只有在运行时才能确定对象的具体类型。impl Trait
是一种语法糖,在编译时就可以确定具体类型,在返回类型中使用,便于编写代码。
0x03 小结
文章主要认识 trait object
,根据 trait object
的特点,我们可以通过 trait object
实现多态,让代码变得更加简洁和灵活。另外,我们也需要更加深入的了解 trait object
,下一篇文章我们来了解下 trait object
在内存的表现形式。
转载自:https://juejin.cn/post/7224619567346581563