【Rust自学】10.4. trait Pt.2:trait作为参数和返回类型、trait bound
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
说句题外话,写这篇的时间比写所有权还还花的久,trait是真的比较难理解的概念。

10.4.1. 把trait作为参数
继续以上一篇文章中所讲的内容作为例子:
pub trait Summary {fn summarize(&self) -> String;
}pub struct NewsArticle {pub headline: String,pub location: String,pub author: String,pub content: String,
}impl Summary for NewsArticle {fn summarize(&self) -> String {format!("{}, by {} ({})", self.headline, self.author, self.location)}
}pub struct Tweet {pub username: String,pub content: String,pub reply: bool,pub retweet: bool,
}impl Summary for Tweet {fn summarize(&self) -> String {format!("{}: {}", self.username, self.content)}
}
在这里我们再新定义一个函数notify,这函数接收NewsArticle和Tweet两个结构体,打印:“Breaking news:”,后面的内容是在参数上调用Summary上的summerize方法的返回值。
但这里有一个问题,它接收的参数是两个结构体,怎么样实现让参数可以是两个类型呢?
我们细想一下,这两个结构体的共同点是什么?没错,它们都实现了Summary这个trait。Rust对于这种情况提供了解决方案:
pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize());
}
只要把参数类型写成impl 某个trait就可以,这里两个结构体都实现了Summary这个trait,所以就写impl Summary,而又因为这个函数不需要数据的所有权,所以写成引用&impl Summary即可。如果又有其它数据类型实现了Summary,那它照样可以作为参数传进去。
impl trait的语法适用于简单情况,针对复杂情况,一般使用trait bound语法。
同样是上面的代码,用trait bound这么写:
pub fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize());
}
这两种写法等价。
但是这种简单的写法看不出来trait bound的优势,再换一个例子。比如说,我要设计一个新的nnotify函数,叫它notify1吧,它接收两个参数,输出"Breaking news:"后面的内容是两个参数分别调用Summary上的summerize方法的返回值。
trait bound写法:
pub fn notify1<T: Summary>(item1: &T, item2: &T) { println!("Breaking news! {} {}", item1.summarize(), item2.summarize());
}
impl trait写法:
pub fn notify1(item1: &impl Summary, item2: &impl Summary) { println!("Breaking news! {} {}", item1.summarize(), item2.summarize());
}
前一种的函数签名显然比后一种的要跟好写也更直观。
而实际上,impl trait写法不过是trait bound写法的语法糖,所以impl trait写法不适合复杂情况也确实可以理解。
那么如果这个notify函数我需要它的参数是同时实现Display这个trait和Summary这个trait呢?也就是如果我有两个甚至两个以上的trait bounds该怎么写呢?
看例子:
pub fn notify_with_display<T: Summary + std::fmt::Display>(item: &T) { println!("Breaking news! {}", item);
}
使用+号连接各个trait bound即可。
还有一点,由于Display不在预导入模块,所以写它的时候需要把路径写出来,也可以在代码开头先引入Display这个trait,也就是写use std::fmt::Display,这样就可以在写trait bound时直接写Display:
use std::fmt::Displaypub fn notify_with_display<T: Summary + Display>(item: &T) { println!("Breaking news! {}", item);
}
别忘了impl trait这个语法糖哦,在这个语法糖里也是用+连接trait bounds:
use std::fmt::Displaypub fn notify_with_display(item: &impl Summary + Display) { println!("Breaking news! {}", item);
}
这种写法有一个缺点,如果trait bounds过多,那么写的大量约束信息就会降低这个函数签名的可读性。为了解决这个问题,Rust提供了替代语法,就是在函数签名之后使用where字句来写trait bounds。
看个使用普通写法的写多个trait bounds:
use std::fmt::Display;
use std::fmt::Debug;pub fn special_notify<T: Summary + Display, U: Summary + Debug>(item1: &T, item2: &U) { format!("Breaking news! {} and {}", item1.summarize(), item2.summarize());
}
使用where字句重写的代码:
use std::fmt::Display;
use std::fmt::Debug;pub fn special_notify<T, U>(item1: &T, item2: &U)
where T: Summary + Display, U: Summary + Debug,
{ format!("Breaking news! {} and {}", item1.summarize(), item2.summarize());
}
这种写法跟C#很相似。
10.4.2. 把trait作为返回类型
跟作为参数一样,把trait作为返回值也可以使用impl trait。如下例:
fn returns_summarizable() -> impl Summary { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, }
}
这个语法有一个缺点:如果让返回类型实现了某个trait,那么必须保证这个函数/方法它所有的可能返回值都只能是一个类型。这是因为impl写法在工作上有一些限制导致Rust不支持。但Rust支持动态派发,之后会讲。
举个例子:
fn returns_summarizable(flag:bool) -> impl Summary { if flag { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, } } else { NewsArticle { headline: String::from("Penguins win the Stanley Cup Championship!"), location: String::from("Pittsburgh, PA, USA"), author: String::from("Iceburgh, Scotland"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), } }
}
根据flag的布尔值一共有两种可能的返回值类型:Tweet类型和NewArticle,这时候编译器就会报错:
error[E0308]: `if` and `else` have incompatible types--> src/lib.rs:42:9|
32 | / if flag {
33 | | / Tweet {
34 | | | username: String::from("horse_ebooks"),
35 | | | content: String::from(
36 | | | "of course, as you probably already know, people",
... | |
39 | | | retweet: false,
40 | | | }| | |_________- expected because of this
41 | | } else {
42 | | / NewsArticle {
43 | | | headline: String::from("Penguins win the Stanley Cup Championship!"),
44 | | | location: String::from("Pittsburgh, PA, USA"),
45 | | | author: String::from("Iceburgh, Scotland"),
... | |
49 | | | ),
50 | | | }| | |_________^ expected `Tweet`, found `NewsArticle`
51 | | }| |_______- `if` and `else` have incompatible types|
help: you could change the return type to be a boxed trait object|
31 | fn returns_summarizable(flag:bool) -> Box<dyn Summary> {| ~~~~~~~ +
help: if you change the return type to expect trait objects, box the returned expressions|
33 ~ Box::new(Tweet {
34 | username: String::from("horse_ebooks"),
...
39 | retweet: false,
40 ~ })
41 | } else {
42 ~ Box::new(NewsArticle {
43 | headline: String::from("Penguins win the Stanley Cup Championship!"),
...
49 | ),
50 ~ })|
报错内容就是if和else下的返回类型是不兼容的(也就是不是同一种类型)。
使用trait bounds的实例
还记得在 10.2. 泛型 中提到的比大小的代码吗?我把代码粘在这里:
fn largest<T>(list: &[T]) -> T{ let mut largest = list[0]; for &item in list{ if item > largest{ largest = item; } } largest
}
当时这么写报的错我也粘在这里:
error[E0369]: binary operation `>` cannot be applied to type `T`--> src/main.rs:4:17|
4 | if item > largest{| ---- ^ ------- T| || T|
help: consider restricting type parameter `T`|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T{| ++++++++++++++++++++++
在学了trait之后,是不是对这种写法和这个报错信息的理解又不同了呢?
先从报错代码来分析,报错信息是比较大小的运算符>不能应用在类型T上,下面的help这行又写了考虑限制类型参数T,再往下看下面还写到了具体的做法,就是在T后面添加std::cmp::PartialOrd(在trait bound里只需要写PartialOrd,因为它在预导入模块内,所以不需要把路径写全),这实际上是一个用于实现比较大小的trait,试试按照提示来改:
fn largest<T: PartialOrd>(list: &[T]) -> T{ let mut largest = list[0]; for &item in list{ if item > largest{ largest = item; } } largest
}
还是报错:
error[E0508]: cannot move out of type `[T]`, a non-copy slice--> src/main.rs:2:23|
2 | let mut largest = list[0];| ^^^^^^^| || cannot move out of here| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait|
help: if `T` implemented `Clone`, you could clone the value--> src/main.rs:1:12|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T{| ^ consider constraining this type parameter with `Clone`
2 | let mut largest = list[0];| ------- you could clone this value
help: consider borrowing here|
2 | let mut largest = &list[0];| +
但报的错不一样了:无法从list里移动元素,因为list里的T没有实现Copy这个trait,下边的help说如果T实现了Clone这个trait,考虑克隆这个值。再下面还有一个help,说考虑使用借用的形式。
根据以上信息,有三种解决方案:
- 为泛型添加上
Copy这个trait - 使用克隆(得为泛型加上
Clone这个trait) - 使用借用
该选择哪个解决方案呢?这取决于你的需求。我想要这个函数能够处理数字和字符的集合,由于数字和字符都是存储在栈内存上的,所以都实现了Copy这个trait,那么只需要为泛型添加上Copy这个trait就可以:
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T{ let mut largest = list[0]; for &item in list{ if item > largest{ largest = item; } } largest
} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!("The largest number is {}", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!("The largest char is {}", result);
}
输出:
The largest number is 100
The largest char is y
那如果我想要这个函数实现String集合的对比呢?由于String是存储在堆内存上的,所以它并没有实现Copy这个trait,所以为泛型添加上Copy这个trait的思路就行不通。
那就试试克隆(得为泛型加上Clone这个trait):
fn largest<T: PartialOrd + Clone>(list: &[T]) -> T{ let mut largest = list[0].clone(); for &item in list.iter() { if item > largest{ largest = item; } } largest
} fn main() { let string_list = vec![String::from("dev1ce"), String::from("Zywoo")]; let result = largest(&string_list); println!("The largest string is {}", result);
}
输出:
error[E0507]: cannot move out of a shared reference--> src/main.rs:3:18|
3 | for &item in list.iter() { | ---- ^^^^^^^^^^^| || data moved here| move occurs because `item` has type `T`, which does not implement the `Copy` trait|
help: consider removing the borrow|
3 - for &item in list.iter() {
3 + for item in list.iter() { |
错误是数据无法移动,因为这种写法要求实现Copy这个trait,但String做不到,该怎么办呢?
那就不让数据移动,不要使用模式匹配,去掉&item前的&,这样item就从T变为了不可变引用&T。然后在比较的时候再使用解引用符号*,把&T解引用为T来与largest比较(下面的代码使用的就是这种),或在largest前加&来变为&T,总之要保持比较的两个变量类型一致:
fn largest<T: PartialOrd + Clone>(list: &[T]) -> T{ let mut largest = list[0].clone(); for item in list.iter() { if *item > largest{ largest = item.clone(); } } largest
}fn main() { let string_list = vec![String::from("dev1ce"), String::from("Zywoo")]; let result = largest(&string_list); println!("The largest string is {}", result);
}
记住T没有实现Copy这个trait,所以在给largest时要使用clone方法。
输出:
The largest string is dev1ce
这里这么写是因为返回值是T,如果把返回值改为&T就不需要克隆了:
fn largest<T: PartialOrd>(list: &[T]) -> &T{ let mut largest = &list[0]; for item in list.iter() { if item > &largest{ largest = item; } } largest
} fn main() { let string_list = vec![String::from("dev1ce"), String::from("Zywoo")]; let result = largest(&string_list); println!("The largest string is {}", result);
}
但是记住,得在largest初始化时得把它设为&T,所以list[0]前得加上&表示引用。而且比较的时候也不能使用给item解引用的方法而得给largest加&。
10.4.3. 使用trait bound有条件的实现方法
在使用泛型类型参数的impl块上使用trait boud,就可以有条件地为实现了特定trait的类型来实现方法。
看个例子:
use std::fmt::Display; struct Pair<T> { x: T, y: T,
} impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } }
} impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } }
}
无论T具体是什么类型,在Pair上都会有new函数,但只有T实现了Display和PartialOrd的时候才会有cmd_display这个方法。
也可以为实现了其它trait的任意类型有条件的实现某个trait。为满足trait bound的所有类型上实现trait叫做覆盖实现(blanket implementations)
以标准库中的to_string函数为例:
impl<T: Display> ToString for T {// ......
}
它的意思就是对所满足display trait的类型都实现了ToString这个trait,这就是所谓的覆盖实现,也就是可以为任何实现了display trait的类型调用ToString这个trait上的方法。
以整数为例:
let s = 3.to_string();
这个操作之所以能实现是因为i32实现了Display trait,所以可以调用ToString上的to_string方法。
相关文章:
【Rust自学】10.4. trait Pt.2:trait作为参数和返回类型、trait bound
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 说句题外话,写这篇的时间比写所有权还还花的久,trait是真的比较难理解的概念。 10.4.1. 把trait作为参数 继续以…...
嵌入式系统 (2.嵌入式硬件系统基础)
2.嵌入式硬件系统基础 2.1嵌入式硬件系统的组成 嵌入式硬件系统以嵌入式微处理器为核心,主要由嵌入式微处理器、总线、存储器、输入/输出接口和设备组成。 嵌入式微处理器 嵌入式微处理器采用冯诺依曼结构或哈佛结构:前者指令和数据共享同一存储空间…...
Linux 下 Vim 环境安装踩坑问题汇总及解决方法(重置版)
导航 安装教程导航 Mamba 及 Vim 安装问题参看本人博客:Mamba 环境安装踩坑问题汇总及解决方法(初版)Linux 下Mamba 及 Vim 安装问题参看本人博客:Mamba 环境安装踩坑问题汇总及解决方法(重置版)Windows …...
OpenAI 故障复盘 - 阿里云容器服务与可观测产品如何保障大规模 K8s 集群稳定性
本文作者: 容器服务团队:刘佳旭、冯诗淳 可观测团队:竺夏栋、麻嘉豪、隋吉智 一、前言 Kubernetes(K8s)架构已经是当今 IT 架构的主流与事实标准(CNCF Survey[1])。随着承接的业务规模越来越大,用户也在使…...
安卓触摸对焦
1. 相机坐标说明 触摸对焦需要通过setFocusAreas()设置对焦区域,而该方法的参数的坐标,与屏幕坐标并不相同,需要做一个转换。 对Camera(旧版相机API)来说,相机的坐标区域是一个2000*2000,原点…...
jupyter出现“.ipynb appears to have died. It will restart automatically.”解决方法
原因 解决方法:更新jupyter的版本 1.打开anaconda prompt 2、更新jupyter版本 在anaconda prompt输入以下指令 conda update jupyter如图:...
20250108-实验+神经网络
实验3. 神经网络与反向传播算法 3.1 计算图:复合函数的计算图 实验要求1:基于numpy实现 ( y 1 , y 2 ) f ( x 1 , x 2 , x 3 ) (y_1,y_2) f(x_1,x_2,x_3) (y1,y2)f(x1,x2,x3) 的反向传播算法(不允许使用自动微分)&a…...
【权限管理】CAS(Central Authentication Service)
CAS(Central Authentication Service)是一种广泛应用的 单点登录(SSO) 协议,它允许用户在一个集中式的身份验证系统中登录一次后,便可以无缝访问多个应用系统,而无需重复登录。CAS 通过统一的身…...
Golang笔记:使用net包进行TCP监听回环测试
文章目录 前言TCP监听回环代码演示 附:UDP监听回环 前言 TCP是比较基础常用的网络通讯方式,这篇文章将使用Go语言实现TCP监听回环测试。 本文中使用 Packet Sender 工具进行测试,其官网地址如下: https://packetsender.com/ TC…...
《浮岛风云》V1.0中文学习版
《浮岛风云》中文版https://pan.xunlei.com/s/VODadt0vSGdbrVOBEsW9Xx8iA1?pwdy7c3# 一款有着类似暗黑破坏神的战斗系统、类似最终幻想的奇幻世界和100%可破坏体素环境的动作冒险RPG。...
Day10——爬虫
爬虫概念 网络请求 爬虫分类 基本流程 请求头...
10. C语言 函数详解
本章目录: 前言1. C 语言函数概述1.1 函数的定义与结构1.2 函数声明1.3 函数调用 2. 函数参数传递2.1 传值调用2.2 传引用调用(模拟)2.3 引用调用(C 特性) 3. 内部函数与外部函数3.1 内部函数3.2 外部函数3.3 示例:多个…...
NRC优先级中比较特殊的—NRC0x13和NRC0x31
1、基础知识 大家都了解 NRC0x13,表示长度错误和格式错误 NRC0x31,表示DID不支持和数据格式不支持 2、为什么说这两个NRC比较特殊 看下图的标注部分: 2.1、先看NRC0x13 步骤一:仔细看是先判断Minmun Length Check ࿰…...
ref() 和 reactive() 区别
ref() 和 reactive() 都是 Vue 3 中用于创建响应式数据的方法,但它们之间存在一些关键差异。 首先,ref() 用于创建响应式的标量值,比如数字、字符串、布尔值等基本数据类型,以及对象和数组等复杂数据类型。当你使用 ref() 时&…...
深度学习与计算机视觉 (博士)
文章目录 零、计算机视觉概述一、深度学习相关概念1.学习率η2.batchsize和epoch3.端到端(End-to-End)、序列到序列(Seq-to-Seq)4.消融实验5.学习方式6.监督学习的方式(1)有监督学习(2)强监督学习(3)弱监督学习(4)半监督学习(5)自监督学习(6)无监督学习(7)总结:不同…...
Sprint Boot教程之五十:Spring Boot JpaRepository 示例
Spring Boot JpaRepository 示例 Spring Boot建立在 Spring 之上,包含 Spring 的所有功能。由于其快速的生产就绪环境,使开发人员能够直接专注于逻辑,而不必费力配置和设置,因此如今它正成为开发人员的最爱。Spring Boot 是一个基…...
NaVILA:用于足式机器人导航的VLA模型
论文地址:https://navila-bot.github.io/static/navila_paper.pdf 项目地址:https://navila-bot.github.io/ 本文提出了一种名为NaVILA的机器人导航模型,旨在解决视觉语言导航问题,并允许机器人在更具挑战性和杂乱的场景中进行导…...
大语言模型提示技巧(七)-扩展
扩展是将较短的文本,例如一组提示或主题列表,输入到大型语言模型中,让模型生成更长的文本。我们可以利用这个特性让大语言模型生成基于某个主题的电子邮件或小论文。通过这种方式使用大语言模型,可以为工作与生活提供诸多便利&…...
基类指针指向派生类对象,基类指针的首地址永远指向子类从基类继承的基类首地址
文章目录 基类指针指向派生类对象,基类指针的首地址永远指向子类从基类继承的基类起始地址。代码代码2 基类指针指向派生类对象,基类指针的首地址永远指向子类从基类继承的基类起始地址。 代码 #include <iostream> using namespace std;class b…...
25年01月HarmonyOS应用基础认证最新题库
判断题 “一次开发,多端部署”指的是一个工程,一次开发上架,多端按需部署。为了实现这一目的,HarmonyOS提供了多端开发环境,多端开发能力以及多端分发机制。 答案:正确 《鸿蒙生态应用开发白皮书》全面阐释…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程
鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...
