Rust编程与项目实战-特质(Trait)

【图书介绍】《Rust编程与项目实战》-CSDN博客

《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

Rust编程与项目实战_夏天又到了的博客-CSDN博客

特质(Trait)是Rust中的概念,类似于其他语言中的接口(Interface)。trait定义了一个可以被共享的行为,只要实现了trait,就能使用该行为。

如果不同的类型具有相同的行为,就可以定义一个trait,然后为这些类型实现该trait。定义trait是把一些方法组合在一起,目的是定义一个实现某些目标所必需的行为的集合。例如,现在有圆形和长方形两个结构体,它们都可以拥有周长和面积。因此,我们可以定义被共享的行为,只要实现了trait就可以使用。

pub trait Figure {                    // 为几何图形定义名为Figure的traitfn girth(&self) -> u64;           // 计算周长fn area(&self) -> u64;            // 计算面积}

这里使用trait关键字来声明一个trait,Figure是trait名。在花括号中定义了该trait的所有方法,在这个例子中有两个方法,分别是fn girth(&self) -> u64;和fn area(&self) -> u64;,trait只定义行为看起来是什么样的,而不定义行为具体是什么样的。因此,我们只定义trait方法的签名,而不进行实现,此时方法签名结尾是“;”,而不是一个 {}。

接下来,每一个实现这个trait的类型都需要具体实现该trait的相应方法,编译器也会确保任何实现Figure trait的类型都拥有与fn girth(&self) -> u64;和fn area(&self) -> u64;签名的定义完全一致的方法。

Rust语言中的物质是非常重要的概念。在Rust中,物质这个概念承担了多种职责,熟悉C++的同学看到这里,会觉得物质和C++的纯虚函数非常类似,而熟悉Go语言的同学看到这里会觉得和Go语言的接口非常类似。但物质的职责远比接口多。物质中可以包含函数、常量、类型等。

8.5.1  成员方法

我们在trait中定义了一个成员方法,代码如下:

trait Shape {fn area(&self) -> f64;}

所有的trait中都有一个隐藏的类型Self(大写S),代表当前实现了此trait的具体类型。trait中定义的函数也可以称作关联函数(Associated Function)。函数的第一个参数如果是Self相关的类型,且命名为self(小写s),这个参数就可以被称为receiver(接收者)。具有receiver参数的函数称为“方法”(Method),可以通过变量实例使用小数点来调用。没有receiver参数的函数称为“静态函数”(Static Function),可以通过类型加双冒号“::”的方式来调用。在Rust中,函数和方法没有本质区别。

Rust中的Self(大写S)和self(小写s)都是关键字,Self的是类型名,self是变量名。请大家一定注意区分。self参数同样也可以指定类型,当然这个类型是有限制的,必须是包装在Self类型之上的类型。对于第一个self参数,常见的类型有self:Self、self:&Self、self:&mut Self等。对于以上这些类型,Rust提供了一种简化的写法,我们可以将参数简写为self、&self、&mut self。self参数只能用在第一个参数的位置。请注意,“变量self”和“类型Self”的大小写不同。比如:

trait T {fn method1(self: Self);fn method2(self: &Self);fn method3(self: &mut Self);}

这段代码和下面的写法是完全一样的:

trait T {fn method1(self);fn method2(&self);fn method3(&mut self);}

我们可以为某些具体类型实现(impl)这个Shape trait。假如有一个结构体类型Circle,它实现了这个特质,代码如下:

struct Circle {radius: f64,}impl Shape for Circle {// Self的类型是Circle// self的类型是&Self,即&Circlefn area(&self) -> f64 {// 访问成员变量,需要用self.radiusstd::f64::consts::PI * self.radius * self.radius}}fn main() {let c = Circle { radius : 2f64};// 第一个参数名字是self,可以使用小数点语法调用println!("The area is {}", c.area());}

另外,针对一个类型,可以直接通过关键字impl给该类型定义成员方法,且无须trait名字。    比如:

impl Circle {fn get_radius(&self) -> f64 { self.radius }}

我们可以把这段代码看作为Circle类型定义了一个匿名的trait。用这种方式定义的方法叫作这个类型的“内在方法”(Inherent Methods)。

trait中可以包含方法的默认实现。如果这个方法在trait中已经有了方法体,那么在针对具体类型实现的时候,就可以选择不用重写。当然,如果需要针对特殊类型进行特殊处理,也可以选择重新实现。比如,在标准库中,迭代器(Iterator)这个trait中就包含10多个方法,但是,其中只有fn next(&mut self)- >OptionSelf::Item是没有默认实现的。其他的方法均有其默认实现,在实现迭代器的时候只需挑选需要重写的方法来实现即可。

self参数甚至可以是Box指针类型self:Box。另外,目前Rust设计组也在考虑让self变量的类型放得更宽,允许更多的自定义类型作为receiver,比如MyType。例如下面的代码:

trait Shape {fn area(self: Box<Self>) -> f64;}struct Circle {radius: f64,}impl Shape for Circle {// Self的类型就是Circle// self的类型是Box<Self>,即Box<Circle>fn area(self : Box<Self>) -> f64 {// 访问成员变量,需要用self.radiusstd::f64::consts::PI * self.radius * self.radius}}fn main() {let c = Circle { radius : 2f64};// 编译错误// c.area();let b = Box::new(Circle {radius : 4f64});// 编译正确b.area();}

impl的对象甚至可以是trait。示例如下:

trait Shape {fn area(&self) -> f64;}trait Round {fn get_radius(&self) -> f64;}struct Circle {radius: f64,}impl Round for Circle {fn get_radius(&self) -> f64 { self.radius }}impl Trait for Trait impl Shape for Round {//为满足T:Round具体类型增加一个成员方法fn area(&self) -> f64 {std::f64::consts::PI * self.get_radius() * self.get_radius()}   }fn main() {let c = Circle { radius : 2f64};// 编译错误// c.area();let b = Box::new(Circle {radius : 4f64}) as Box<Round>;// 编译正确b.area();}

impl Shape for Round和impl<T:Round>Shape for T是不一样的。在前一种写法中,self是&Round类型,它是一个指向trait的指针,即trait Object。而在后一种写法中,self是&T类型,是具体类型。前一种写法是为trait Object增加一个成员方法,而后一种写法是为所有满足T:Round的具体类型增加一个成员方法。所以上面的示例中,我们只能在构造trait Object之后才能调用area()成员方法。

impl Shape for Round这种写法确实很让初学者纠结,Round既是trait又是Type。将来trait Object的语法会被要求加上dyn关键字。

8.5.2  静态方法

没有receiver参数的方法(第一个参数不是self参数的方法)称作静态方法。静态方法可以通过Type::FunctionName()的方式调用。需要注意的是,即便第一个参数是Self相关类型,只要变量名字 不是self,就不能使用小数点的语法调用函数。

struct T(i32);impl T {// 这是一个静态方法fn func(this: &Self) {println!{"value {}", this.0};}}fn main() {let x = T(42);// x.func(); 小数点方式调用是不合法的T::func(&x);}

在标准库中就有一些这样的例子。Box的一系列方法Box:: into_raw(b:Self)Box::leak(b:Self),以及Rc的一系列方法 Rc::try_unwrap(this:Self)Rc::downgrade(this:&Self)都是这种情况。它们的receiver不是self关键字,这样设计的目的是强制用户用Rc::downgrade(&obj)的形式调用,而禁止用obj.downgrade()的形式调用。这样源码表达出来的意思更清晰,不会因为Rc里面的成员方法和T里面的成员方法重名而造成误解。

trait中也可以定义静态函数。下面以标准库中的std::default:: Default trait为例介绍静态函数的相关用法:

pub trait Default {fn default() -> Self;}

上面这个trait中包含一个default()函数,它是一个无参数的函数,返回的类型是实现该trait的具体类型。Rust中没有构造函数的概念。Default trait实际上可以看作一个针对无参数构造函数的统一抽象。比如在标准库中,Vec::default()就是一个普通的静态函数。

impl<T> Default for Vec<T> {fn default() -> Vec<T> {Vec::new()}}

跟C++相比,在Rust中,定义静态函数没必要使用static关键字,因为它把self参数显式地在参数列表中列出来了。作为对比,C++里面的成员方法默认可以访问this指针,因此它需要用static关键字来标记静态方法。Rust不采取这个设计,主要原因是self参数的类型变化太多,不同写法的语义差别很大,选择显式声明self参数更方便指定它的类型。

8.5.3  扩展方法

我们还可以利用trait给其他的类型添加成员方法,哪怕这个类型不是我们自己写的。比如,可以为内置类型i32添加一个方法:

trait Double {fn double(&self) -> Self;}impl Double for i32 {fn double(&self) -> i32 { *self * 2 }}fn main() {// 可以像成员方法一样调用let x : i32 = 10.double();println!("{}", x);}

哪怕这个类型不是在当前 的项目中声明的,依然可以为它增加一些成员方法。但也不是随便就可以这么做,Rust对此有一个规定:在声明trait和impltraitl的时候,Rust规定了Coherence Rule(一致性规则)或称为Orphan Rule(孤儿规则):imp块要么与trait的声明在同一个crate中,要么与类型的声明在同一个crate中。

这是有意设计的。如果我们在使用其他crate的时候,强行把它们“拉郎配”,是会制造出Bug的。比如说,我们写了一个程序,引用了外部库lib1和lib2,lib1中声明了一个trait T,lib2中声明了一个struct S,我们不能在自己的程序中针对S实现T。这也意味着,上游开发者在给别人写库的时候,尤其要注意,一些比较常见的标准库中的trait,如Display Debug ToString Default等,应该尽可能提供好。否则,使用这个库的下游开发者是没办法帮我们实现这些trait的。同理,如果是匿名impl,那么这个impl块必须与类型本身存在于同一个crate中。

Rust是一种用户可以对内存有精确控制能力的强类型语言。我们可以自由指定一个变量是在栈里面还是在堆里面,变量和指针也是不同的类型。类型是有大小(Size)的。有些类型的大小在编译阶段就可以确定,有些类型的大小在编译阶段无法确定。目前版本的Rust规定,在函数参数传递、返回值传递等地方,都要求这个类型在编译阶段有确定的大小。否则,编译器就不知道该如何生成代码了。而trait本身既不是具体类型,也不是指针类型,它只是定义了针对类型的、抽象的“约束”。不同的类型可以实现同一个trait,满足同一个trait的类型可能具有不同的大小。因此,trait在编译阶段没有固定大小,目前我们不能直接使用trait作为实例变量、参数、返回值。比如:

let x: Shape = Circle::new();             // Shape不能做局部变量的类型fn use_shape(arg : Shape) {}               // Shape不能直接做参数的类型fn ret_shape() -> Shape {}                // Shape不能直接做返回值的类型

这样的写法是错误的,请一定要记住。trait的大小在编译阶段是不固定的,需要写成dynShape形式,即编译的时候把不确定大小的内容通过胖指针来代替,而指针在编译期是确定的。胖指针包含一个指向数据的指针和数据的长度信息,在Rust中,数组、切片、trait对象等都是胖指针。

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

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

相关文章

uniapp中使用全局样式文件引入的三种方式

如果你想在 uni-app 中全局引入 SCSS 文件&#xff08;例如 global.scss&#xff09;&#xff0c;可以通过以下步骤进行配置&#xff1a; 方法一&#xff1a;在 main.js 中引入 在 main.js 中引入全局样式&#xff1a; 你可以在 src/main.js 文件中直接引入 SCSS 文件&#xff…

运维之systemd 服务(Systemd Service of Operations and Maintenance)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

Vue — 组件化开发

组件化开发&#xff1a;一个页面可以拆分成一个个组件&#xff1b;每个组件都有自己独立的结构、样式、行为 组件分类&#xff1a;普通组件、根组件 其中根组件包裹着所有普通小组件 普通组件的注册使用&#xff1b;有两种注册方式 局部注册全局注册 局部注册 目标&#xff…

【学习】【HTML】HTML、XML、XHTML

HTML 什么是 HTML&#xff1f; HTML (HyperText Markup Language) 是一种用于创建和展示网页的标准标记语言。它由一系列的元素组成&#xff0c;这些元素通过标签的形式来告诉浏览器如何显示内容。 HTML 的基本结构是什么&#xff1f; <!DOCTYPE html> <html> …

【软考】系统架构设计师-计算机系统基础(2):操作系统

1、操作系统基础 OS的5个核心功能&#xff1a;进程管理、存储管理、设备管理、文件管理、作业管理 OS的3个作用&#xff1a;管理运行的程序和分配各种软硬件资源&#xff1b;提供友善的人机界面&#xff1b;为程序应用的开发和运行提供高效的平台 OS的4个特征&#xff1a;并…

Android ANR分析总结

1、ANR介绍 ANR&#xff08;Application Not Responding&#xff09;指的是应用程序无响应&#xff0c;当Android应用程序在主线程上执行长时间运行的操作或阻塞I/O操作时发生。这可能导致应用程序界面冻结或无法响应用户输入。 1、Service ANR&#xff1a;前台20s&#xff0…

WebRTC视频 01 - 视频采集整体架构

一、前言&#xff1a; 我们从1对1通信说起&#xff0c;假如有一天&#xff0c;你和你情敌使用X信进行1v1通信&#xff0c;想象一下画面是不是一个大画面中有一个小画面&#xff1f;这在布局中就叫做PIP&#xff08;picture in picture&#xff09;&#xff1b;这个随手一点&am…

编译ffmpeg动态库时设置RPATH为$ORIGIN

原本&#xff0c;我这样编译: ./configure \--enable-xxx \--disable-yyy \...为了设置 RPATH, 尝试了在 configure 后面设置&#xff0c;如下几种都无效: --extra-ldsoflags"-Wl,-rpath,$ORIGIN" 没有 RPATH--extra-ldsoflags"-Wl,-rpath,$ORIGIN" 没有…

什么是 DAPP?它能解决什么问题?

在区块链技术日益火热的今天&#xff0c;DAPP 这个概念也逐渐走入人们的视野。但是很多人都听到了DAPP这个词&#xff0c;但是大部分人却还是不清楚什么是 DAPP&#xff1f;它又能解决什么问题呢&#xff1f;接下来这篇文章就带大家了解一下DAPP。 一、什么是 DAPP&#xff1f…

C++ 中的 JSON 序列化和反序列化:结构体与枚举类型的处理

在 C 编程中&#xff0c;处理 JSON 数据是一项常见任务&#xff0c;特别是在需要与其他系统或前端进行数据交换时。nlohmann::json 库是一个功能强大且易于使用的 JSON 库&#xff0c;它允许我们轻松地在 C 中进行 JSON 数据的序列化和反序列化。本文将详细介绍如何使用 nlohma…

ESLint 使用教程(三):12个ESLint 配置项功能与使用方式详解

前言 在现代前端开发中&#xff0c;代码质量与一致性是至关重要的&#xff0c;ESLint 正是为此而生的一款强大工具&#xff0c;本文将带您详细了解 ESLint 的配置文件&#xff0c;并通过通俗易懂的方式讲解其主要配置项及其配置方法。此外&#xff0c;我们还将探讨一些高级配置…

linux可执行文件添加到PATH环境变量的方法

linux可执行文件添加到PATH环境变量的方法 linux命令行下面执行某个命令的时候&#xff0c;首先保证该命令是否存在&#xff0c;若存在&#xff0c;但输入命令的时候若仍提示&#xff1a;command not found 这个时候就的查看PATH环境变量的设置了&#xff0c;当前命令是否存在于…

Android 14 SPRD 下拉菜单中增加自动亮度调节按钮

为了在 Android 14 的下拉菜单中增加自动亮度调节按钮&#xff0c;可以按照以下步骤进行代码修改。 1. 添加图标资源 在 SystemUI 资源文件夹中添加自动亮度图标&#xff1a; 文件路径&#xff1a; frameworks/base/packages/SystemUI/res/drawable/ic_settings_display_wh…

Jenkins应用详解(Detailed Explanation of Jenkins Application)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【LeetCode】【算法】5. 最长回文子串

LeetCode 5. 最长回文子串 题目描述 给你一个字符串s&#xff0c;找到 s 中最长的回文子串。 思路 思路&#xff1a;中心扩散法 遍历字符串s&#xff0c;对每一个字符用中心扩散法。确定好中点之后向两边扩散&#xff0c;若两边字符相同则len2。若遍历下来len>maxLen则记…

开源数据库 - mysql - mysql-server-8.4(gtid主主同步+ keepalived热切换)部署方案

前置条件 假设主从信息 mysqlhostport主192.168.1.13306从192.168.1.23306vip192.168.1.3 部署流程 导出测试环境表结构与数据 使用mysqldump ./mysqldump -ulzzc -p -S /tmp/mysql3306.sock --single-transaction --database lzzc > databaseLZZCxxxx.sql查看gtid号 …

【Python】计算机视觉应用:OpenCV库图像处理入门

计算机视觉应用&#xff1a;OpenCV库图像处理入门 在当今的数字化时代&#xff0c;计算机视觉&#xff08;Computer Vision&#xff09;已经渗透到各行各业&#xff0c;比如自动驾驶、智能监控、医疗影像分析等。而 Python 的 OpenCV 库&#xff08;Open Source Computer Visi…

【LeetCode】【算法】33. 搜索旋转排序数组

LeetCode 33. 搜索旋转排序数组 题目描述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k…

【C++】 list 与 string 基础与实现字符串操作

【C】使用 list 与 string 实现基础字符串操作 文章目录 一、字符串的基础操作1.1 - startsWith1.2 - endsWith1.3 - trim1.4 - indexOf1.5 - replaceAll 二、list 基础操作2.1 - 遍历2.1.1 - 使用迭代器访问2.1.2 - 使用基于范围的 for 循环遍历2.1.3 - 使用标准算法库遍历 2.…

ctfshow-web入门-反序列化(web260-web264)

目录 1、web260 2、web261 3、web262 4、web263 5、web264 1、web260 要求传入的内容序列化后包含指定内容即可&#xff0c;在 PHP 序列化中&#xff0c;如果键名或值包含 ctfshow_i_love_36D&#xff0c;那么整个序列化结果也会包含这个字符串。 payload&#xff1a; ?…