【Rust自学】19.2. 高级trait:关联类型、默认泛型参数和运算符重载、完全限定语法、supertrait和newtype

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

19.2.1. 在trait定义中使用关联类型来指定占位类型

我们首先在第10章的10.3. trait Pt.1:trait的定义、约束与实现 和 10.4. trait Pt.2:trait作为参数和返回类型、trait bound 中介绍了trait,但我们没有讨论更高级的细节。现在我们来深入了解

关联类型(associated type)是trait中的类型占位符,它可以用于trait方法的签名中。它用于定义出包某些类型的trait,而在实现前无需知道这些类型是什么。

看个例子:

pub trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;
}

标准库中的负责迭代器部分的iterator trait(详见 13.8. 迭代器 Pt.4:创建自定义迭代器)就是一个带有关联类型的trait,其定义如上。

Item就是关联类型。在迭代过程中使用Item类型来替代实际出现的值以完成逻辑和实际数据类型分离的目的。可以看到next方法的返回值Option<Self:Item>就出现了Item

Item就是所谓的类型占位符,其核心思想与泛型有点像,但区别也是有的:

泛型关联类型
每次实现 Trait 时标注类型无需标注类型
可以为一个类型多次实现某个 Trait(不同的泛型参数)无法为单个类型多次实现某个 Trait

19.2.2. 默认泛型参数和运算符重载

我们可以在使用泛型参数时为泛型指定一个默认的具体类型。它的语法是<PlaceholderType=ConcreteType>。这种技术常用于运算符重载(operator overloading)。

虽然Rust不允许创建自己的运算符及重载任意的运算符,但是可以通过实现std::ops中列出的那些trait来重载一部分相应的运算符。

看个例子:

use std::ops::Add;#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {x: i32,y: i32,
}impl Add for Point {type Output = Point;fn add(self, other: Point) -> Point {Point {x: self.x + other.x,y: self.y + other.y,}}
}fn main() {assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },Point { x: 3, y: 3 });
}
  • 我们在这个例子中为point结构体实现了Add trait,也就是重载了+这个运算符,具体来说就是Add trait下的add函数把每个字段分别相加
  • 主函数里就可以直接使用+来把两个Point类型相加

Add trait的定义如下:

trait Add<Rhs=Self> {type Output;fn add(self, rhs: Rhs) -> Self::Output;
}

它就使用了默认的泛型参数类型Rhs=Self。也就是说当我们实现Add trait时如果没有为Rhs指定一个具体的类型,那么Rhs的类型就默认为Self,所以上文的例子中的Rhs就是Point

再看一个例子,这回我们想要实现毫米和米相加的例子:

use std::ops::Add;struct Millimeters(u32);
struct Meters(u32);impl Add<Meters> for Millimeters {type Output = Millimeters;fn add(self, other: Meters) -> Millimeters {Millimeters(self.0 + (other.0 * 1000))}
}
  • 这里先通过结构体字段声明了MillimetersMeters,分别表示毫米和米。
  • 下文为Millimeters实现了Add trait,其中又通过<Meters>显式地指明了类型被设定为Meters了。add函数中通过本身的毫米和传进来的米乘1000相加得出来以毫米计数的值。

19.2.3. 默认泛型参数的主要应用场景

  • 扩展一个类型而不破坏现有代码
  • 允许在大部分用户都不需要的特定场景下进行自定义

19.2.4. 完全限定语法(Fully Qualified Syntax)如何调用同名方法

直接看例子:

trait Pilot {fn fly(&self);
}trait Wizard {fn fly(&self);
}struct Human;impl Pilot for Human {fn fly(&self) {println!("This is your captain speaking.");}
}impl Wizard for Human {fn fly(&self) {println!("Up!");}
}impl Human {fn fly(&self) {println!("*waving arms furiously*");}
}
  • 定义了两个trait,分别叫PilotWizard,都有一个fly方法,没有具体实现
  • 有一个结构体叫Human,我们在下文为它分别实现了那两个trait,也就是分别为两个trait写了fly方法。除此之外,还通过impl块为结构体本身实现了fly方法。

这个时候一共有三个fly方法,如果我们在主函数中调用:

fn main() {let person = Human;person.fly();
}

运行这段代码会打印出*waving arms furiously* ,表明Rust直接调用了Human上实现的fly方法。

要从Pilot trait或Wizard trait调用fly方法,我们需要使用更明确的语法来指定我们指的是哪个fly方法:

fn main() {let person = Human;Pilot::fly(&person);Wizard::fly(&person);person.fly();
}

在方法名称之前指定特征名称可以向 Rust 阐明我们要调用哪个fly实现。person.fly()也可以写 Human::fly(&person)

输出:

This is your captain speaking.
Up!
*waving arms furiously*

但是,不是方法的关联函数没有self 范围。当有多个类型或trait定义的方法具有相同函数名时,Rust 并不总是知道指的是哪种类型,除非使用完全限定语法

trait Animal {fn baby_name() -> String;
}struct Dog;impl Dog {fn baby_name() -> String {String::from("Spot")}
}impl Animal for Dog {fn baby_name() -> String {String::from("puppy")}
}fn main() {println!("A baby dog is called a {}", Dog::baby_name());
}
  • Animal trait有baby_name方法。Dog是一个结构体,实现了Animal trait,同时也通过impl块实现了baby_name方法。这是一共就有两个baby_name方法。
  • 在主函数里使用了Dog::baby_name(),按照上文的逻辑就应该执行Dogimpl块实现的baby_name方法,也就是输出"Spot"。

输出:

A baby dog is called a Spot

那怎么实现Dog实现的Animal trait上的baby_name方法呢?我们试试使用上一个例子的逻辑:

fn main() {println!("A baby dog is called a {}", Animal::baby_name());
}

输出:

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type--> src/main.rs:20:43|
2  |     fn baby_name() -> String;|     ------------------------- `Animal::baby_name` defined here
...
20 |     println!("A baby dog is called a {}", Animal::baby_name());|                                           ^^^^^^^^^^^^^^^^^^^ cannot call associated function of trait|
help: use the fully-qualified path to the only available implementation|
20 |     println!("A baby dog is called a {}", <Dog as Animal>::baby_name());|                                           +++++++       +For more information about this error, try `rustc --explain E0790`.
error: could not compile `traits-example` (bin "traits-example") due to 1 previous error

Animal trait上的baby_name函数的执行需要知道是哪个类型上的实现,但是baby_name这个方法又没有参数,所以不知道是哪个类型上的实现。

针对这种情况就得使用完全限定语法。其形式为:

<Type as Trait>::function(receiver_if_method, next_arg, ...);

这种语法可以在任何调用函数或方法的地方使用,并且它允许忽略那些从其它上下文推导出来的部分。

但是这种语法只有在Rust无法区分你期望调用哪个具体实现的时候才需要使用这种语法,因为这种语法写起来太麻烦了,所以轻易不使用。

根据这个语法上面的代码就应该这么改:

fn main() {println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

输出:

A baby dog is called a puppy

19.2.5. 使用supertrait来要求trait附带其它trait的功能

有时候我们可能会需要在一个trait中使用其它trait的功能,也就是说间接依赖的trait也需要被实现。而那个被间接依赖的trait就是当前trait的supertrait。

看个例子:

use std::fmt;trait OutlinePrint: fmt::Display {fn outline_print(&self) {let output = self.to_string();let len = output.len();println!("{}", "*".repeat(len + 4));println!("*{}*", " ".repeat(len + 2));println!("* {output} *");println!("*{}*", " ".repeat(len + 2));println!("{}", "*".repeat(len + 4));}
}

OutlinePrint实际上是用来在终端通过字符打印一个图形的。但是在打印过程中必须要求self实现了to_string方法,也就是要求self实现了Display trait(to_stringDisplay trait下的方法)。其写法就是trait关键字 + trait名字 + : + supertrait。

假如我们有一个结构体Point,想要通过OutlinePrint trait的outline_print函数在终端打印出来。又因为OutlinePrint trait需要Display trait的函数,所以得为它同时实现OutlinePrint trait和Display trait,不然就会报错:

struct Point {x: i32,y: i32,
}use std::fmt;impl fmt::Display for Point {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "({}, {})", self.x, self.y)}
}impl OutlinePrint for Point {}

19.2.6. 使用newtype模式在外部类型上实现外部trait

我们之前讲过一个孤儿规则:只有当trait或类型定义在本地包时,才能为该类型实现这个trait。而我们可以使用newtype模式来绕过这一规则,具体来说就是利用元组结构体来构建一个新的类型放在本地。

看个例子:

我们想为Vector实现Display trait,但是VectorDisplay trait都定义在在外部包中,所以无法直接为Vector实现。所以把Vector包裹在自己创建的元组结构体Wrapper里,然后用Wrapper来实现Display trait:

use std::fmt;struct Wrapper(Vec<String>);impl fmt::Display for Wrapper {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "[{}]", self.0.join(", "))}
}fn main() {let w = Wrapper(vec![String::from("hello"), String::from("world")]);println!("w = {w}");
}

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

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

相关文章

Elasticsearch:如何搜索含有复合词的语言

作者&#xff1a;来自 Elastic Peter Straer 复合词在文本分析和标记过程中给搜索引擎带来挑战&#xff0c;因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。 德语以其长复合词而闻名&#xff1a;Rindfleischetik…

web-SQL注入-CTFHub

前言 在众多的CTF平台当中&#xff0c;作者认为CTFHub对于初学者来说&#xff0c;是入门平台的不二之选。CTFHub通过自己独特的技能树模块&#xff0c;可以帮助初学者来快速入门。具体请看官方介绍&#xff1a;CTFHub。 作者更新了CTFHub系列&#xff0c;希望小伙伴们多多支持…

WPS动画:使图形平移、围绕某个顶点旋转一定角度

1、平移 案例三角形如下图&#xff0c;需求&#xff1a;该三角形的A点平移至原点 &#xff08;1&#xff09;在预想动画结束的位置绘制出图形 &#xff08;2&#xff09;点击选中原始图像&#xff0c;插入/动画/绘制自定义路径/直线 &#xff08;3&#xff09;十字星绘制的直线…

xmind使用教程

xmind使用教程 前言xmind版本信息“xmind使用教程”的xmind思维导图 前言 首先xmind是什么&#xff1f;XMind 是一款思维导图和头脑风暴工具&#xff0c;用于帮助用户组织和可视化思维、创意和信息。它允许用户通过图形化的方式来创建、整理和分享思维导图&#xff0c;可以用于…

KNIME:开源 AI 数据科学

KNIME&#xff08;Konstanz Information Miner&#xff09;是一款开源且功能强大的数据科学平台&#xff0c;由德国康斯坦茨大学的软件工程师团队开发&#xff0c;自2004年推出以来&#xff0c;广泛应用于数据分析、数据挖掘、机器学习和可视化等领域。以下是对KNIME的深度介绍…

2025年01月27日Github流行趋势

项目名称&#xff1a;onlook项目地址url&#xff1a;https://github.com/onlook-dev/onlook项目语言&#xff1a;TypeScript历史star数&#xff1a;5340今日star数&#xff1a;211项目维护者&#xff1a;Kitenite, drfarrell, iNerdStack, abhiroopc84, apps/dependabot项目简介…

TCL C++开发面试题及参考答案

进程和线程的区别 进程和线程都是操作系统中重要的概念,它们在很多方面存在着明显的区别。 从概念上来说,进程是资源分配的基本单位,每个进程都有自己独立的地址空间、内存、文件描述符等资源。例如,当我们在计算机上同时运行多个应用程序,像浏览器、文本编辑器等,每个应…

本地部署DeepSeek-R1模型(新手保姆教程)

背景 最近deepseek太火了&#xff0c;无数的媒体都在报道&#xff0c;很多人争相着想本地部署试验一下。本文就简单教学一下&#xff0c;怎么本地部署。 首先大家要知道&#xff0c;使用deepseek有三种方式&#xff1a; 1.网页端或者是手机app直接使用 2.使用代码调用API …

VS Code 复制正确格式的文件路径/文件夹路径 (绝对路径,相对路径, 斜杠 /, 反斜杠\\ 等)

VS Code 搜索 : baincd.copy-path-unixstyle Github : https://github.com/baincd/vscode-copy-path-unixstyle 插件市场: https://marketplace.visualstudio.com/items?itemNamebaincd.copy-path-unixstyle 支持复制各种格式的路径 格式 GitBash /c/chris/project-name/sr…

每天学点小知识之设计模式的艺术-策略模式

行为型模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解模板方法模式 模板方法模式是结构最简单的行为型设计模式&#xff0c;在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式&#xff0c;可以将一些复杂流程的实现步骤封装在一系列基…

python 中的堆

文章目录 小根堆的特点Python 中的 heapq 模块1. heapq.heappush(heap, item)2. heapq.heappop(heap)3. heapq.heapify(x)4. heapq.heappushpop(heap, item)5. heapq.heapreplace(heap, item)6. heapq.nsmallest(n, iterable)7. heapq.nlargest(n, iterable) 小根堆的应用场景示…

深度学习 Pytorch 基础网络手动搭建与快速实现

为了方便后续练习的展开&#xff0c;我们尝试自己创建一个数据生成器&#xff0c;用于自主生成一些符合某些条件、具备某些特性的数据集。 导入相关的包 # 随机模块 import random# 绘图模块 import matplotlib as mpl import matplotlib.pyplot as plt# 导入numpy import nu…

【RocketMQ】RocketMq之IndexFile深入研究

一&#xff1a;RocketMq 整体文件存储介绍 存储⽂件主要分为三个部分&#xff1a; CommitLog&#xff1a;存储消息的元数据。所有消息都会顺序存⼊到CommitLog⽂件当中。CommitLog由多个⽂件组成&#xff0c;每个⽂件固定⼤⼩1G。以第⼀条消 息的偏移量为⽂件名。 ConsumerQue…

注解与反射基础

注解 概述 注解&#xff08;Annotation&#xff09;&#xff0c;从jdk5.0引入。 作用 不是程序本身&#xff0c;可以对程序作出解释&#xff08;这一点和注释没什么区别&#xff09;可以被其他程序读取 格式 注释是以“注释名”在代码中存在的&#xff0c;还可以添加一些…

SliverAppBar的功能和用法

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容&#xff0c;本章回中将介绍SliverAppBar组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似&#xff0c;它们的…

BFS(广度优先搜索)——搜索算法

BFS&#xff0c;也就是广度&#xff08;宽度&#xff09;优先搜索&#xff0c;二叉树的层序遍历就是一个BFS的过程。而前、中、后序遍历则是DFS&#xff08;深度优先搜索&#xff09;。从字面意思也很好理解&#xff0c;DFS就是一条路走到黑&#xff0c;BFS则是一层一层地展开。…

数据库 - Sqlserver - SQLEXPRESS、由Windows认证改为SQL Server Express认证进行连接 (sa登录)

本文讲SqlServer Express版本在登录的时候&#xff0c; 如何由Windows认证&#xff0c;修改为Sql Server Express认证。 目录 1&#xff0c;SqlServer Express的Windows认证 2&#xff0c;修改为混合认证 3&#xff0c;启用sa 用户 4&#xff0c;用sa 用户登录 下面是详细…

GWO优化SVM回归预测matlab

灰狼优化算法&#xff08;Grey Wolf Optimizer&#xff0c;简称 GWO&#xff09;&#xff0c;是由澳大利亚格里菲斯大学的 Mirjalii 等人于 2014 年提出的群智能优化算法。该算法的设计灵感源自灰狼群体的捕食行为&#xff0c;核心思想是对灰狼社会的结构与行为模式进行模仿。 …

elasticsearch8.15 高可用集群搭建(含认证Kibana)

文章目录 1.资源配置2.系统参数优化3.JDK17安装4.下载&安装ES 8.155.生成ES的证书(用于ES节点之间进行安全数据传输)6.修改ES 相关配置文件7.创建es用户并启动8.配置ES的账号和密码(用于ES服务端和客户端)9.下载和安装Kibana10.编辑Kibana配置文件11.启动Kiabana12.访问Kia…

地址查询API接口:高效查询地址信息,提升数据处理效率

地址查询各省市区API接口 地址查询是我们日常生活中经常遇到的一个需求&#xff0c;无论是在物流配送、地图导航还是社交网络等应用中&#xff0c;都需要通过地址来获取地理位置信息。为了满足这个需求&#xff0c;我们可以使用地址查询API接口来高效查询地址信息&#xff0c;提…