【Rust自学】10.4. trait Pt.2:trait作为参数和返回类型、trait bound

news2025/1/7 23:59:41

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

说句题外话,写这篇的时间比写所有权还还花的久,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,这函数接收NewsArticleTweet两个结构体,打印:“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::Display

pub fn notify_with_display<T: Summary + Display>(item: &T) {  
    println!("Breaking news! {}", item);  
}

别忘了impl trait这个语法糖哦,在这个语法糖里也是用+连接trait bounds:

use std::fmt::Display

pub 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 ~         })
   |

报错内容就是ifelse下的返回类型是不兼容的(也就是不是同一种类型)。

使用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实现了DisplayPartialOrd的时候才会有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方法。

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

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

相关文章

R机器学习:神经网络算法的理解与实操,实例解析

神经网络算法是一种模仿生物神经网络&#xff08;尤其是人脑&#xff09;结构和功能的算法。它由大量相互连接的节点&#xff08;称为神经元&#xff09;组成&#xff0c;这些神经元组织成层&#xff0c;通过传递信号来处理信息。神经网络算法在机器学习、人工智能等领域中扮演…

Java(day4)

二维数组 静态初始化 动态初始化 练习 public class test1 {public static void main(String[]args){int arr[][]{{22,66,44},{77,33,88},{25,45,65},{11,66,99}};int sum0;for(int i0;i<arr.length;i){int a0;for(int j0;j<arr[i].length;j){sumarr[i][j];aarr[i][j];…

element-plus大版本一样,但是小版本不一样导致页面出bug

npm 的版本 node的版本 npm的源这些都一样&#xff0c;但是效果不一样 发现是element的包版本不一样导致的 2.9.1与2.8.1的源是不一样的&#xff0c;导致页面出bug;

【Docker】安装registry本地镜像库,开启Https功能

下载镜像 docker pull registry:2 需要启动https功能&#xff0c;就要生成服务端的自签名的证书和私钥&#xff0c;以及在docker客户端安装这个经过签名的证书。 第一步&#xff1a;生成公私钥信息&#xff0c;第二步&#xff0c;制作证书签名申请文件&#xff0c; 第三步&…

单片机-LED点阵实验

要将第一个点点亮&#xff0c;则 1 脚接高电平 a 脚接低电平&#xff0c;则第一个点就亮了&#xff1b;如果要将第一行点亮&#xff0c;则第 1 脚要接高电平&#xff0c;而&#xff08;a、b、c、d、e、f、g、h &#xff09;这些引脚接低电平&#xff0c;那么第一行就会点亮&…

PDFMathTranslate: Star13.8k,一款基于AI的PDF文档全文双语翻译PDF文档全文双语翻译,保留格式神器,你应该需要它

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 PDFMathTranslate是一个开源项目&#xff0c;旨在为用户提供便捷的PDF科学论文翻译解决方案。它不仅能够翻译文本&#xff0c;还能保留公式、图表、目…

RockyLinux9配置静态ip地址教程

以往我们配置linux系统的ip地址是在 /etc/sysconfig/network-scripts/ifcfg-网卡名 配置文件中编辑的&#xff0c;详情请见 Rocky8.10配置网络和主机名教程_rocky8配置网络-CSDN博客 但是在RockyLinux9系统中弃用了以前的这种方式&#xff0c;改为了新的配置方式。下面我们介绍…

民宿酒店预订系统小程序+uniapp全开源+搭建教程

一.介绍 一.系统介绍 基于ThinkPHPuniappuView开发的多门店民宿酒店预订管理系统&#xff0c;快速部署属于自己民宿酒店的预订小程序&#xff0c;包含预订、退房、WIFI连接、吐槽、周边信息等功能。提供全部无加密源代码&#xff0c;支持私有化部署。 二.搭建环境 系统环境…

【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1. 带权有向图 2. 图的邻接矩阵 3. 图的邻接表 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;编写一个程序实现图的邻接矩阵和邻接表的存储。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 带权有向图…

【数据挖掘】深度高斯过程

深度高斯过程&#xff08;Deep Gaussian Process, DGP&#xff09;是一种结合高斯过程&#xff08;Gaussian Process, GP&#xff09;和深度学习的模型&#xff0c;旨在将高斯过程的非参数灵活性与深度模型的分层特征学习能力相结合。它可以看作是高斯过程的深度扩展&#xff0…

【NLP 18、新词发现和TF·IDF】

目录 一、新词发现 1.新词发现的衡量标准 ① 内部稳固 ② 外部多变 2.示例 ① 初始化类 NewWordDetect ② 加载语料信息&#xff0c;并进行统计 ③ 统计指定长度的词频及其左右邻居字符词频 ④ 计算熵 ⑤ 计算左右熵 ​编辑 ⑥ 统计词长总数 ⑦ 计算互信息 ⑧ 计算每个词…

GitLab创建用户,设置访问SSH Key

继上一篇 Linux Red Hat 7.9 Server安装GitLab-CSDN博客 安装好gitlab&#xff0c;启用管理员root账号后&#xff0c;开始创建用户账户 1、创建用户账户 进入管理后台页面 点击 New User 输入用户名、邮箱等必填信息和登录密码 密码最小的8位&#xff0c;不然会不通过 拉到…

计算机网络--路由表的更新

一、方法 【计算机网络习题-RIP路由表更新-哔哩哔哩】 二、举个例子 例1 例2

C语言 数组编程练习

1.将数组A的内容和数组B中的内容进行交换。&#xff08;数组一样大&#xff09; 2.创建一个整形数组&#xff0c;完成对数组的操作 实现函数Init()初始化数组全为0 实现print()打印数组的每个元素 实现reverse()函数完成数组元素的逆置 //2.创建一个整形数组&#xff0c;完…

H7-TOOL固件2.27发布,新增加40多款芯片脱机烧录,含多款车轨芯片,发布LUA API手册,CAN助手增加负载率,错误状态信息检测

H7-TOOL详细介绍&#xff08;含操作手册&#xff09;&#xff1a;H7-TOOL开发工具&#xff0c;1拖4/16脱机烧录&#xff0c;高速DAPLINK&#xff0c;RTOS Trace&#xff0c;CAN/串口助手, 示波器, RTT等&#xff0c;支持WiFi&#xff0c;以太网&#xff0c;高速USB和手持 - H7-…

基于Matlab的变压器仿真模型建模方法(13):单相升压自耦变压器的等效电路和仿真模型

1.单相升压自耦变压器的基本方程和等效电路 单相升压自耦变压器的接线原理图如图1所示。在建立自耦变压器的基本方程时,仍然把它看成是从双绕组变压器演变而来。在图1中,设节点a到节点b部分的绕组的匝数为,对应于双绕组变压器的原边绕组;节点c到节点a部分的绕组的绕组匝数为…

Linux——查看并修改文件夹可读可写等权限

一、查看当前文件夹下的所有文件夹的权限 ls -l二、命令 drwxrwxr-x&#xff1a;只有所有者有可读可写可执行权限&#xff0c;其他用户只有可读可执行权限。 sudo chmod -R 775 文件夹名字drwxrwxrwx&#xff1a;所有用户都有可读可写可执行权限。 sudo chmod -R 777 文件夹…

【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1. 纯虚函数 一、特点 二、使用场景 三、作用 四、注意事项 五、相关概念对比 2. 抽象类的使用 一、定义与概念 二、使用场景 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;设计一个矩形类、一个圆形…

杰发科技——使用ATCLinkTool解除读保护

0. 原因 在jlink供电电压不稳定的情况下&#xff0c;概率性出现读保护问题&#xff0c;量产时候可以通过离线烧录工具避免。代码中开了读保护&#xff0c;但是没有通过can/uart/lin/gpio控制等方式进行关闭&#xff0c;导致无法关闭读保护。杰发所有芯片都可以用本方式解除读保…

【pytorch-lightning】架构一览

pytorch-lightning是基于pytorch的一个套壳项目&#xff0c;适配pytorch的版本同步更新速度很快。 它将训练的几个主要流程模块化&#xff0c;减少重复工作&#xff0c;同时让支持分布式训练&#xff0c;不同平台的训练迁移变得更加简单。 官网链接