我的RUST学习—— 【第十七章 17-2】为使用不同类型的值而设计的 trait 对象

2024-02-29 10:08

本文主要是介绍我的RUST学习—— 【第十七章 17-2】为使用不同类型的值而设计的 trait 对象,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

现在有个场景,我要编写一个GUI库,首先需要一个screen类,用于收纳组件,其中有许多组件components,只需要生成组件实例,并挂载到screen上,最后遍历screen中的组件,并调用其上的 draw 方法,就可以绘制到屏幕上。

我作为库的开发者,我只实现Screen类,并且实现一个Button Component。使用我们的库的人,可以实现它自己的组件,只需要保证为他的库实现draw方法即可。

思考传统面向对象语言如何实现?

首先,定义一个父类,Component,类上有 draw 方法,其他子类,比如 ButtonImage等组件需要继承自Component类,并覆盖实现自己的draw方法。

不过,Rust并没有自己的继承,需要另寻出路!

定义通用行为的Trait

定义 Draw Trait

为了实现组件期望的通用行为draw,我定义一个 Draw Trait ,其中包含 draw 方法。

// lib.rs
pub trait Draw {fn draw (&self);
}

创建 Screen 结构体

接着创建Screen struct,并且使它拥有一个存放 Trait 对象的容器。

Trait 对象是指实现了该Trait 的结构体实例(也可以是其他数据结构)。

pub struct Screen {pub components: Vec<Box<dyn Draw>>,
}
impl Screen {pub fn run (&self) {for component in self.components.iter() {component.draw();}}
}

为什么使用鸭子类型?

注意这里容器中 components 的类型,Box<dyn Draw>,Box可以理解,表示智能指针,dyn Draw 是指所有实现了Draw Trait的类型,即Trait对象。

什么是鸭子类型?

只关心值所反映的信息而不是其具体类型:如果它走起来像一只鸭子,叫起来像一只鸭子,那么它就是一只鸭子!

在此处,我们不关心具体的类型,只关心这个类型有没有实现Draw Trait。

为什么不使用泛型?要进行动态分发?

第十章:泛型代码的性能 一节讲到,对泛型进行 trait bound时,编译器会进行单态化处理,即在编译时就能推断出 T 的具体类型,并且固定。单态化所产生的的代码会被静态分发。

而此处,使用 trait 对象时,必须使用动态分发,因为编译器无法知晓 trait 对象的类型,这是一个鸭子类型,其真实的类型可能是多种多样的。

这一次操作,会带来性能上的退化,但为了额外的灵活性,仍然需要权衡取舍。

创建 Button 组件

pub struct Button {pub width: u32,pub height: u32,pub label: String,
}impl Draw for Button {fn draw(&self) {// -- snip --}
}

库的使用者创建自己的代码

// main.rs
use gui::Draw;
use gui::{Button, Screen};struct SelectBox {width: u32,height: u32,options: Vec<String>,
}impl Draw for SelectBox {fn draw(&self) {// -- snip --}
}fn main() {let btn = Button {width: 10,height: 5,label: String::from("Ok"),};let sbox = SelectBox {width: 20,height: 10,options: vec![String::from("male"), String::from("female")],};let components: Vec<Box<dyn Draw>> = vec![Box::new(btn), Box::new(sbox)];let screen = Screen {components,};screen.run();
}

Trait 对象要求对象安全

只有 对象安全(object safe)的 trait 才可以组成 trait 对象。使得 trait 对象安全的属性存在复杂的规则,不过在实践中,只涉及到两条。如果一个 trait 中所有的方法有如下属性时,则该 trait 是对象安全的:

  • 返回值类型不为 Self
  • 方法没有任何泛型类型参数

一个不是对象安全的例子是标准库中的 Clone trait。Clone trait 的 clone 方法的参数签名看起来像这样:

pub trait Clone {fn clone(&self) -> Self;
}pub struct Screen {pub components: Vec<Box<dyn Clone>>,
}

将会得到如下错误:

error[E0038]: the trait `std::clone::Clone` cannot be made into an object--> src/lib.rs:2:5|
2 |     pub components: Vec<Box<dyn Clone>>,|     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone`cannot be made into an object|= note: the trait cannot require that `Self : Sized`

如果你对对象安全的更多细节感兴趣,请查看 Rust RFC 255。

这篇关于我的RUST学习—— 【第十七章 17-2】为使用不同类型的值而设计的 trait 对象的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/758400

相关文章

使用Python自建轻量级的HTTP调试工具

《使用Python自建轻量级的HTTP调试工具》这篇文章主要为大家详细介绍了如何使用Python自建一个轻量级的HTTP调试工具,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录一、为什么需要自建工具二、核心功能设计三、技术选型四、分步实现五、进阶优化技巧六、使用示例七、性能对比八、扩展方向建

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将