Rust泛型与trait特性,模仿接口的实现

泛型是一个编程语言不可或缺的机制。

C++ 语言中用"模板"来实现泛型,而 C 语言中没有泛型的机制,这也导致 C 语言难以构建类型复杂的工程。

泛型机制是编程语言用于表达类型抽象的机制,一般用于功能确定、数据类型待定的类,如链表、映射表等。

在函数中定义泛型

这是一个对整型数字选择排序的方法:

实例

fn max(array: &[i32]) -> i32 {let mut max_index = 0;let mut i = 1;while i < array.len() {if array[i] > array[max_index] {max_index = i;}i += 1;}array[max_index]
}fn main() {let a = [2, 4, 6, 3, 1];println!("max = {}", max(&a));
}

运行结果:

max = 6

这是一个简单的取最大值程序,可以用于处理 i32 数字类型的数据,但无法用于 f64 类型的数据。通过使用泛型我们可以使这个函数可以利用到各个类型中去。但实际上并不是所有的数据类型都可以比大小,所以接下来一段代码并不是用来运行的,而是用来描述一下函数泛型的语法格式:

实例

fn max<T>(array: &[T]) -> T {let mut max_index = 0;let mut i = 1;while i < array.len() {if array[i] > array[max_index] {max_index = i;}i += 1;}array[max_index]
}

结构体与枚举类中的泛型

在之前我们学习的 Option 和 Result 枚举类就是泛型的。

Rust 中的结构体和枚举类都可以实现泛型机制。

struct Point<T> {x: T,y: T
}

这是一个点坐标结构体,T 表示描述点坐标的数字类型。我们可以这样使用:

let p1 = Point {x: 1, y: 2};
let p2 = Point {x: 1.0, y: 2.0};

使用时并没有声明类型,这里使用的是自动类型机制,但不允许出现类型不匹配的情况如下:

let p = Point {x: 1, y: 2.0};

x 与 1 绑定时就已经将 T 设定为 i32,所以不允许再出现 f64 的类型。如果我们想让 x 与 y 用不同的数据类型表示,可以使用两个泛型标识符:

struct Point<T1, T2> {x: T1,y: T2
}

在枚举类中表示泛型的方法诸如 Option 和 Result:

enum Option<T> {Some(T),None,
}enum Result<T, E> {Ok(T),Err(E),
}

结构体与枚举类都可以定义方法,那么方法也应该实现泛型的机制,否则泛型的类将无法被有效的方法操作。

实例

struct Point<T> {x: T,y: T,
}impl<T> Point<T> {fn x(&self) -> &T {&self.x}
}fn main() {let p = Point { x: 1, y: 2 };println!("p.x = {}", p.x());
}

运行结果:

p.x = 1

注意,impl 关键字的后方必须有 <T>,因为它后面的 T 是以之为榜样的。但我们也可以为其中的一种泛型添加方法:

impl Point<f64> {fn x(&self) -> f64 {self.x}
}

impl 块本身的泛型并没有阻碍其内部方法具有泛型的能力:

impl<T, U> Point<T, U> {fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {Point {x: self.x,y: other.y,}}
}

方法 mixup 将一个 Point<T, U> 点的 x 与 Point<V, W> 点的 y 融合成一个类型为 Point<T, W> 的新点。


特性

特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法。

特性在 Rust 中用 trait 表示:

trait Descriptive {fn describe(&self) -> String;
}

Descriptive 规定了实现者必需有 describe(&self) -> String 方法。

我们用它实现一个结构体:

实例

struct Person {name: String,age: u8
}impl Descriptive for Person {fn describe(&self) -> String {format!("{} {}", self.name, self.age)}
}

格式是:

impl <特性名> for <所实现的类型名>

Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个。

默认特性

这是特性与接口的不同点:接口只能规范方法而不能定义方法,但特性可以定义方法作为默认方法,因为是"默认",所以对象既可以重新定义方法,也可以不重新定义方法使用默认的方法:

实例

trait Descriptive {fn describe(&self) -> String {String::from("[Object]")}
}struct Person {name: String,age: u8
}impl Descriptive for Person {fn describe(&self) -> String {format!("{} {}", self.name, self.age)}
}fn main() {let cali = Person {name: String::from("Cali"),age: 24};println!("{}", cali.describe());
}

运行结果:

Cali 24

如果我们将 impl Descriptive for Person 块中的内容去掉,那么运行结果就是:

[Object]

特性做参数

很多情况下我们需要传递一个函数做参数,例如回调函数、设置按钮事件等。在 Java 中函数必须以接口实现的类实例来传递,在 Rust 中可以通过传递特性参数来实现:

fn output(object: impl Descriptive) {println!("{}", object.describe());
}

任何实现了 Descriptive 特性的对象都可以作为这个函数的参数,这个函数没必要了解传入对象有没有其他属性或方法,只需要了解它一定有 Descriptive 特性规范的方法就可以了。当然,此函数内也无法使用其他的属性与方法。

特性参数还可以用这种等效语法实现:

fn output<T: Descriptive>(object: T) {println!("{}", object.describe());
}

这是一种风格类似泛型的语法糖,这种语法糖在有多个参数类型均是特性的情况下十分实用:

fn output_two<T: Descriptive>(arg1: T, arg2: T) {println!("{}", arg1.describe());println!("{}", arg2.describe());
}

特性作类型表示时如果涉及多个特性,可以用 + 符号表示,例如:

fn notify(item: impl Summary + Display)
fn notify<T: Summary + Display>(item: T)

注意:仅用于表示类型的时候,并不意味着可以在 impl 块中使用。

复杂的实现关系可以使用 where 关键字简化,例如:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)

可以简化成:

fn some_function<T, U>(t: T, u: U) -> i32where T: Display + Clone,U: Clone + Debug

在了解这个语法之后,泛型章节中的"取最大值"案例就可以真正实现了:

实例

trait Comparable {fn compare(&self, object: &Self) -> i8;
}fn max<T: Comparable>(array: &[T]) -> &T {let mut max_index = 0;let mut i = 1;while i < array.len() {if array[i].compare(&array[max_index]) > 0 {max_index = i;}i += 1;}&array[max_index]
}impl Comparable for f64 {fn compare(&self, object: &f64) -> i8 {if &self > &object { 1 }else if &self == &object { 0 }else { -1 }}
}fn main() {let arr = [1.0, 3.0, 5.0, 4.0, 2.0];println!("maximum of arr is {}", max(&arr));
}

运行结果:

maximum of arr is 5

Tip: 由于需要声明 compare 函数的第二参数必须与实现该特性的类型相同,所以 Self (注意大小写)关键字就代表了当前类型(不是实例)本身。

特性做返回值

特性做返回值格式如下:

实例

fn person() -> impl Descriptive {Person {name: String::from("Cali"),age: 24}
}

但是有一点,特性做返回值只接受实现了该特性的对象做返回值且在同一个函数中所有可能的返回值类型必须完全一样。比如结构体 A 与结构体 B 都实现了特性 Trait,下面这个函数就是错误的:

实例

fn some_function(bool bl) -> impl Descriptive {if bl {return A {};} else {return B {};}
}

有条件实现方法

impl 功能十分强大,我们可以用它实现类的方法。但对于泛型类来说,有时我们需要区分一下它所属的泛型已经实现的方法来决定它接下来该实现的方法:

struct A<T> {}impl<T: B + C> A<T> {fn d(&self) {}
}

这段代码声明了 A<T> 类型必须在 T 已经实现 B 和 C 特性的前提下才能有效实现此 impl 块。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/518281.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink StreamGraph生成过程

文章目录 概要SteramGraph 核心对象SteramGraph 生成过程 概要 在 Flink 中&#xff0c;StreamGraph 是数据流的逻辑表示&#xff0c;它描述了如何在 Flink 作业中执行数据流转换。StreamGraph 是 Flink 运行时生成执行计划的基础。 使用DataStream API开发的应用程序&#x…

SQL server内存问题排查方案

前言 由于昨晚线上服务器数据库突然访问数据缓慢&#xff0c;任务管理里面SQL server进程爆满等等&#xff0c;重大事故的排查拟写解决方案。 整体思路 查询数据库请求连接&#xff1a;排查连接池是否占满查询数据库请求量&#xff1a;排查数据是否存在反复查询查询数据库阻…

【Hadoop大数据技术】——HDFS分布式文件系统(学习笔记)

&#x1f4d6; 前言&#xff1a;Hadoop的核心是HDFS&#xff08;Hadoop Distributed File System&#xff0c;Hadoop分布式文件系统&#xff09;和MapReduce。其中&#xff0c;HDFS是解决海量大数据文件存储的问题&#xff0c;是目前应用最广泛的分布式文件系统。 目录 &#x…

C++复习笔记——泛型编程模板

01 模板 模板就是建立通用的模具&#xff0c;大大提高复用性&#xff1b; 02 函数模板 C另一种编程思想称为 泛型编程 &#xff0c;主要利用的技术就是模板 C 提供两种模板机制:函数模板和类模板 函数模板语法 函数模板作用&#xff1a; 建立一个通用函数&#xff0c;其函…

NextJs教程系列(一):介绍安装

什么是 Next.js Next.js 是一个用于构建全栈 Web 应用程序的 React 框架。您可以使用 React 组件来构建用户界面&#xff0c;并使用 Next.js 来构建其他功能和优化。 Next.js 的特点 构建全栈 Web 应用程序的 React 框架。为 React 提供了开箱即用的服务器端渲染。为 React …

基于java SSM的房屋租赁系统设计和实现

基于java SSM的房屋租赁系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

使用Jenkins CI/CD和Gitee webhooks发布前端自定义组件库到npm

通过之前的学习&#xff0c;沉淀出了一套自定义公共组件库&#xff0c;现在要实现将其通过Jenkins的CICD 推送代码到npm上 一、配置npm &#xff08;服务器命令行窗口上配置&#xff09; 1.设置官方网址 npm config set registry https://registry.npmjs.org/2.登录&#xf…

智慧城市中的数据力量:大数据与AI的应用

目录 一、引言 二、大数据与AI技术的融合 三、大数据与AI在智慧城市中的应用 1、智慧交通 2、智慧环保 3、智慧公共安全 4、智慧公共服务 四、大数据与AI在智慧城市中的价值 1、提高城市管理的效率和水平 2、优化城市资源的配置和利用 3、提升市民的生活质量和幸福感…

四节点/八节点四边形单元悬臂梁Matlab有限元编程 | 平面单元 | Matlab源码 | 理论文本

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

微服务获取登录用户Id与单体服务下获取用户Id对比(黑马头条Day03)

前置声明 当前前后端分离开发项目中&#xff0c;后端某个请求向具体某个数据库中的多个表插入数据时&#xff0c;经常需要使用到当前登录用户的Id&#xff08;唯一标识&#xff09;。在当前用户线程下以实现变量共享&#xff0c;同时为了避免不同用户线程之间操作变量的影响&am…

期货开户如何查询最新的手续费明细?

一、如何查询最新的手续费明细和保证金明细&#xff1f; 1、手机或者电脑交易软件下单窗口&#xff0c;点击品种合约&#xff0c;一般会显示1手需要的保证金比例&#xff0c;比如手机博弈大师/同花顺期货通等。 2、电脑交易软件下单窗口&#xff0c;点合约&#xff0c;里面会…

在idea中如何开启项目的热部署

热部署&#xff1a;就是当我们IDEA的项目在运行期间&#xff0c;我们修改代码以后&#xff0c;不需要我们自己重启项目&#xff0c;IDEA就会自动的重启项目 在idea中开启项目热部署的步骤 第一步&#xff1a;引入热部署的依赖 <dependency><groupId>org.springfr…