likes
comments
collection
share

Rust 中级教程 第18课——trait object (1)本篇文章是 `trait` 篇的知识,由于前面一直没有介

作者站长头像
站长
· 阅读数 35

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;

Rust 中级教程 第18课——trait object (1)本篇文章是 `trait` 篇的知识,由于前面一直没有介

给出的原因是:由于 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 小结Rust 中级教程 第18课——trait object (1)本篇文章是 `trait` 篇的知识,由于前面一直没有介

文章主要认识 trait object ,根据 trait object 的特点,我们可以通过 trait object 实现多态,让代码变得更加简洁和灵活。另外,我们也需要更加深入的了解 trait object,下一篇文章我们来了解下 trait object 在内存的表现形式。

转载自:https://juejin.cn/post/7224619567346581563
评论
请登录