Rust编程-泛型、Trait和生命周期

泛型:

        泛型是类型编程中的一种工具。本质上是类型的变量,目的是提高代码的复用。泛型是
具体类型或其他属性的抽象替代。

        为了复用,我们会使用函数将功能封装,同样,泛型也是为了复用,只不过是为了类型的复用。

        例如:Option<T>、Vec<T>、Hash<K, V>,Result<T,E>。使用的时候这里的类型可以使用具体的类型替换。

泛型与函数封装:

        函数封装步骤:

        1. 找到重复的代码。

        2. 将重复的代码提取至函数体中,考虑函数参数和返回值。

        采用同样步骤考虑函数加入泛型,让函数可以处理更多的参数类型和返回值类型

fn largest<T>(list: &[T]) -> T

泛型与结构体和枚举:

        使用trait来定义通用行为。在定义泛型时,使用trait可以将其限制为拥有某些特定行为的类型,而不是任意类型。 

struct Point<T> {x: T,y: T,
}let wont_work = Point { x: 5, y: 4 }; // T的类型被自动推断为i32struct Point<T,U>{ // 泛型也可以定义多个x:T,y:U,
}

        枚举中定义泛型:

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 { // 返回结构体中属性x的T类型值的引用&self.x}
}

 Point<T>定义了一个名为x的方法,它会返回一个指向字段x中数据的引用

必须紧跟着impl关键字声明T,以便能够在实现方法时指定类型Point<T>,

通过在impl之后将T声明为泛型,Rust能够识别出Point尖括号内的类型是泛型而不是具体类型

因为可以单独为Point<f32>实例而不是所有的Point<T>泛型实例来实现方法。

impl Point<f32> {fn distance_from_origin(&self) -> f32 {(self.x.powi(2) + self.y.powi(2)).sqrt()}
}

 这里的impl代码块只作用于使用具体类型替换了泛型参数T的结构体

这段代码意味着,类型Point<f32>将会拥有一个名为distance_from_origin的方法,而其他的Point<T>实例则没有该方法的定义。

结构体定义中的泛型参数并不总是与我们在方法签名上使用的类型参数一致。

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,}}
}
泛型与生命周期:

        泛型可以向编译器提供引用之间的相互关系,它允许我们在借用值时通过编译器来确保这些引用的有效性。

泛型的性能:

        Rust实现泛型的方式决定了使用泛型的代码与使用具体类型的代码相比不会有任何速度上的差异,原因是

Rust会在编译时执行泛型代码的单态化(monomorphization)。单态化 是一个在编译期将泛型代码转换为特定代码的过程,它会将所有使用过的具体类型填入泛型参数从而得到有具体类型的代码。

          

tait:

        trait(特征)被用来向Rust编译器描述某些特定类型拥有的且能够被其他类型共享的功能,它使我们可以以一种抽象的方式来定义共享行为。

        trait提供了一种将特定方法签名组合起来的途径,它定义了为达成某种目的所必需的行为集合。

        使用trait关键字来声明trait。花括号用于定义类型行为的方法签名。

pub trait Summary {fn summarize(&self) -> String;
}

        一个trait可以包含多个方法:每个方法签名占据单独一行并以分号结尾。

        为类型实现trait:

        

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

   通过impl和for关键字,结构体NewsArticle实现了Summary trait。

  当第三方开发者想要为他们自定义的结构体实现Summary trait并使用相关功能时,就必须将这个trait引入自己的作用域中

 实现trait有一个限制:只有当trait或类型定义于我们的库中时,我们才能为该类型实现对应的trait。

   这段话的意思是,trait和类型,至少一个属于自己的库的时候才可以为类型实现trait:

        1. trait是自己定义的:那么也可以为标准库的类型来impl trait

        2. 类型是自己定义的:那么可以为该类型实现从第三方引入的trait

   不能为外部类型实现外部trait

  trait中的默认实现:

        trait重定义的是抽象的方法声明。但也可以具体实现,即默认实现。

   

pub trait Summary {fn summarize(&self) -> String {String::from("(Read more...)")}
}

     默认实现中调用相同trait中的其他方法,即使该方法并没有默认实现

        

pub trait Summary {fn summarize_author(&self) -> String;fn summarize(&self) -> String {format!("(Read more from {}...)", self.summarize_author())}
}

   可以将trait理解为接口模板,也类似Java里的abstract类。

   使用trait作为参数

        使用trait来定义接收不同类型参数的函数

pub fn notify(item: impl Summary) {// 这里使用impl关键字,表示参数实现了Summary traitprintln!("Breaking news! {}", item.summarize());
} 

在调用notify时向其中传入任意一个实现了Summary的结构体实例

trait约束

        impl Trait是trait约束的语法糖。

        等同于使用泛型:

pub fn notify<T: Summary>(item: T) { // 泛型T添加了约束Summaryprintln!("Breaking news! {}", item.summarize());
}

泛型参数与trait约束同时放置在尖括号中,并使用冒号分隔。

pub fn notify(item1: impl Summary, item2: impl Summary) {pub fn notify<T: Summary>(item1: T, item2: T) { // 相对比,使用泛型更简约

通过+语法来指定多个trait约束

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

使用where从句来简化trait约束

将泛型的约束提取到where语句中:

fn some_function<T, U>(t: T, u: U) -> i32where T: Display + Clone,U: Clone + Debug
{
返回实现了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,}
}

impl Trait可以精练地声明函数会返回实现了trait的类型,而不需要写出具体的类型。

你只能在返回一个类型时使用impl Trait

使用trait约束来有条件地实现方法

通过在带有泛型参数的impl代码块中使用trait约束,我们可以单独为实现了指定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> { // 这里定义的cmp_display方法只能是在实现了Display和PortialOrd的trait的泛型实例上使用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);}}
}

类型Pair<T>都会实现new函数,但只有在内部类型T实现了PartialOrd(用于比较)与 Display(用于打印)这两个trait的前提下,才会实现cmd_display方法。

可以为实现了某个trait的类型有条件地实现另一个trait

impl<T: Display> ToString for T {

标准库对所有满足Display trait约束的类型实现了ToString trait。

之前学到的是for关键字后面是结构体或枚举,但这里是泛型T;impl后面的ToString是trait。

可以为任何实现了Display trait的类型调用ToString trait中的to_string方法

生命周期:

生命周期是另外一种泛型

生命周期能够确保引用在我们的使用过程中一直有效

Rust的每个引用都有生命周期,对应着引用保持有效性的作用域。

生命周期都是隐式且可以被推导出来的,但当引用的生命周期以不同方式相互关联时,必须手动标注生命周期。Rust需要注明泛型生命周期参数之间的关系,确保运行时实际使用的引用一定是有效的。

        生命周期避免悬垂引用:

        

  {let r;{let x = 5;r = &x; // 外部的变量,borrow借用了内部作用域的变量。但是x指向的内存在内部作用域结束的时候会被销毁。}println!("r:{}",r);}

上面代码r借用了内部作用域变量x。当内部作用域结束的时候x变量被销毁。因此,r会指向空的内存地址。如果还有其他代码赋值,可能就引用到非法的地址,造成内存内容泄露,造成安全问题。

        Rust借用检查器:borrow checker

        用于比较不同的作用域并确定所有借用的合法性:

        被引用对象的生命周期(存在范围)是否短于引用者(的生命周期)。

        函数中的泛型生命周期

                返回函数中引用生命周期最短的那个:        

fn longest<'a>(str1:&'a str,str2:&'a str)->&'a str{

               总结:1. 函数参数都是引用

                        2. 其次生命周期参数的标注类似泛型。以'单引号开头,用来区别泛型。

                        3. 手动标注生命周期参数的目的是告诉编译器,参数和返回值的引用生命周期的长度。因为如果不加泛型生命周期,Rust借用检查器无法判断这些引用的作用域范围。(如果单单给出函数的签名,相信我们自己也无法做出判断)

               返回第一个参数引用:

fn longest<'a>(x: &'a str, y: &str) -> &'a str {

              总结:

                        返回的引用和第二个参数没有关系,因此,无需标注生命周期参数

              返回引用和参数没有关系:

fn longest<'a>(x: &str, y: &str) -> &'a str {let result = String::from("really long string");result.as_str()
}

           编译器会报错。因为返回引用和参数都没关系,因此,返回的引用必然是函数内部变量的引用。

           因为函数结束后,内部变量会被销毁,返回的引用会造成悬垂引用。因此,编译器无法通过编译。

        结构体中生命周期标注:

                结构体中定义过自持有类型,也可以在结构体中存储引用,需要为结构体定义中的每一个引用都添加生命周期标注

                

struct ImportantExcept<'a>{part:&'a str,
}

 标注意味着ImportantExcerpt实例的存活时间不能超过存储在part字段中的引用的存活时间。

        生命周期标注语法:

                参数名称以撇号(')开头,使用全小写字符。'a被大部分开发者选择作为默认使用的名称。

                生命周期参数的标注填写在&引用运算符之后,并通过一个空格符来将标注与引用类型区分开来

                如同使用了泛型参数的函数可以接收任何类型一样,使用了泛型生命周期的函数也可以接收带有任何生命周期的引用

                

let novel = String::from("Call me Ishmael.Some years ago...");let first_sentence = novel.split('.').next().expect("Could not find a '.'");let i = ImportantExcerpt{part:first_sentence
};// 标注意味着ImportantExcerpt实例的存活时间不能超过存储在part字段中的引用的存活时间
struct ImportantExcerpt<'a>{ part:&'a str,
}

novel会在ImportantExcerpt离开作用域后才离开作用域,所以ImportantExcerpt实例中的引用总是有效的。

        生命周期省略

任何引用都有一个生命周期,并且需要为使用引用的函数或结构体指定生命周期参数。

fn first_word(s: &str) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[0..i];}}&s[..]
}

 对于可预测的场景,Rust将这些模式直接写入编译器,使用借用检查器自动对这些生命周期进行推导无需显示标注。

这些被写入Rust引用分析部分的模式也就是所谓的生命周期省略规则

函数参数或方法参数中的生命周期被称为输入生命周期(input lifetime),而返回值的生命周期则被称为输出生命周期(output lifetime)

Rust编译器使用了3种规则计算引用的生命周期:

        1. 每一个引用参数都会拥有自己的生命周期参数

        2. 当只存在一个输入生命周期参数时,这个生命周期会被赋予给所有输出生命周期参数(该规则使用上面代码实例)

        3. 当拥有多个输入生命周期参数,而其中一个是&self或&mut self时,self的生命周期会被赋予给所有的输出生命周期参数

方法中声明周期标注方式:

        区别于函数,方法是针对结构体和枚举实现的方法

        声明和使用生命周期参数的位置取决于它们是与结构体字段相关,还是与方法参数返回值相关

        结构体字段中的生命周期:

                生命周期的名字总是需要被声明在impl关键字之后,并被用于结构体名称之后,因为这些生命周期是结构体类型的一部分。

struct ImportantExcerpt<'a>{part:&'a str,
}impl<'a> ImportantExcerpt<'a>{fn level(&self) -> i32{3}
}

&self生命周期参数会应用于输出生命周期参数

声明在impl及类型名称之后的生命周期是不能省略的

impl<'a> ImportantExcerpt<'a>{fn announce_and_return_part(&self,announcement:&str) -> &str{println!("Attention please:{}",announcement);&self.part}
}

&self的生命周期会应用于输出生命周期参数

        静态生命周期:

         静态生命周期'static, 表示整个程序执行期间。

        所有字符换字面量都拥有静态生命周期,因为字符串的文本被直接存储在二进制程序中,并总是可用的。

总结:

        1. 泛型:允许代码应用于不同类型

        2.trait与trait约束:在代码中指定泛型的行为

        3. 生命周期:用来确保代码不会出现悬垂引用

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

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

相关文章

工业大数据是什么?应用工业大数据时面临哪些挑战?

在当今快速发展的工业领域&#xff0c;大数据已成为推动企业转型升级的核心动力。工业大数据&#xff0c;以其独特的价值和潜力&#xff0c;正逐渐改变着传统的生产、管理和决策模式。然而&#xff0c;伴随着大数据的快速发展&#xff0c;一系列挑战也随之浮现。本文将深入探讨…

算法日常练习

对于这个题&#xff0c;如何处理同一个方向的问题&#xff0c;且对于同一组的如果间隔太大如何实现离散化 #include<bits/stdc.h> using namespace std;#define int long long typedef long long ll; map<pair<int,int>,vector<pair<ll,ll>>> mp…

关于机械键盘的购买,该怎么选择?

一.关于轴体的选择。 1.青轴&#xff1a;青轴是机械键盘最有段落感的轴&#xff0c;声音比较大&#xff0c;以吵死人别人著称。有人将其比喻为Cherry的春天&#xff0c;爽快清脆的段落感如春天般舒畅。适合在宿舍、咖啡厅&#xff0c;图书馆使用。&#xff08;我装的 &#xf…

C++ STL std::lexicographical_compare用法和实现

一&#xff1a;功能 按字典顺序比较两个序列&#xff0c;判断第一个序列是否小于(或大于)第二个序列 二&#xff1a;用法 #include <compare> #include <vector> #include <string> #include <algorithm> #include <iostream> #include <fo…

linux源码安装mysql8.0的小白教程

1.下载8.x版本的mysql MySQL :: Download MySQL Community Server (Archived Versions) 2.安装linux 我安装的是Rocky Linux8.6 3.设置ip地址,方便远程连接 使用nmcli或者nmtui设置或修改ip地址 4.使用远程连接工具MobaXterm操作: (1)将mysql8版本的压缩包上传到mybaxterm…

数据建设实践之大数据平台(三)安装hadoop

安装hadoop 上传安装文件到/opt/software目录并解压 [bigdata@node101 software]$ tar -zxvf hadoop-3.3.5.tar.gz -C /opt/services/ 配置环境变量 [bigdata@node101 ~]$ sudo vim /etc/profile.d/bigdata_env.sh export JAVA_HOME=/opt/services/jdk1.8.0_161 export ZK…

一图看懂 | 蓝卓油气行业解决方案

我国是全球最大的能源消费国&#xff0c;保障国家能源安全是我国能源发展的首要任务&#xff0c;油气作为我国能源体系的重要组成部分&#xff0c;是支撑我国工业和经济社会发展的基础和“压舱石&#xff0c;也是必须筑牢的能源安全底线。 蓝卓根据油气田行业发展趋势&#xf…

前端实现一键复制功能

1、下载插件 npm i vue-clipboard32.0.0 2、在需要复制的文件中引入插件并使用&#xff1a; JS: import useClipboard from "vue-clipboard3"; const { toClipboard } useClipboard(); HTML: <el-tooltip content"复制内容" placement"top&…

算法面试题_字节

问题一&#xff1a;Transfomer矩阵维度分析及MultiHead详解&#xff1a; 细致链接1 细致链接2 问题二&#xff1a;transformer的结构&#xff0c;流程&#xff0c;维度变换&#xff0c;encoder&#xff0c;decoder&#xff1a; 多头维度怎么变化&#xff1a;先在q&#xff0…

自然语言处理基本概念

自然语言处理基本概念 所有学习循环神经网络的人都是看这一篇博客长大的&#xff1a; https://colah.github.io/posts/2015-08-Understanding-LSTMs/ import jieba import torch from torch import nns1 "我吃饭了&#xff01;" s2 "今天天气很好&#xff01…

电脑录屏软件哪个效果最好 怎么一边录屏一边直播 电脑录屏软件好用免费推荐

随着科技的发展&#xff0c;电脑的更新迭代也越来越快&#xff0c;各项功能的进步与完善使得人们的工作和生活越来越离不开电脑&#xff0c;其中录屏功能就很好的体现了网络的便利&#xff0c;人们可以将在电脑画面的变化通过录屏功能记录下来&#xff0c;以便后续学习和回顾。…

基于STM32设计的智能手环(ESP8266+华为云IOT)178

基于STM32设计的智能手环(178) 文章目录 一、前言1.1 项目介绍【1】项目功能介绍【2】项目硬件模块组成【3】ESP8266工作模式配置【4】Android手机APP开发思路【5】项目模块划分1.2 项目功能需求(1)生理参数监测(2)计步功能(3)GPS定位(4)时间显示(5)OLED显示屏展示(…

xlwings 链接到 指定sheet 从别的 excel 复制 sheet 到指定 sheet

重点 可以参考 宏录制 cell sheet.range(G4)cell.api.Hyperlinks.Add(Anchorcell.api, Address"", SubAddress"001-000-02301!A1")def deal_excel(self):with xw.App(visibleTrue) as app:wb app.books.open(self.summary_path, update_linksFalse)sheet…

python-小理与他的画(赛氪OJ)

[题目描述] 小理是个画家&#xff0c;他希望有一天他的画能让心仪的她看到。 只是后来她有了他&#xff0c;他却只有他的画&#xff0c;他望着他的画&#xff0c;默默的发呆。 可惜做题的你&#xff0c;画不出他画的她&#xff0c;所以&#xff0c;我们只好画点简单的画&#x…

Python的入门知识(上)

学习目标&#xff1a; 了解python 入门知识 这里写目录标题 学习目标&#xff1a;学习内容&#xff1a;快速入门 Python 基础特殊规则及特殊字符&#xff1a;Python 文件组织&#xff1a;多元赋值&#xff1a;变量命名规则&#xff1a;__name__ 系统变量&#xff1a;内存管理&a…

centos9+mysql8.0下mycat1.6部署

#创作灵感# 整理一下mysql代理技术&#xff0c;这个当时是和mysql集群部署一个项目的&#xff0c;一并整理出来供参考。 1、环境准备 此处使用的为M-M-SS双主双从结构集群&#xff0c;集群部署方法放在我的上一篇文章中 防火墙可以使用firewall-cmd放行&#xff0c;演示环境…

民航飞机维修工卡、放行单推广使用电子签章,每天可省约3万张纸

据某民航公司对外公布数据显示&#xff0c;通过在飞机航线维修工作中应用电子签章&#xff0c;以日均1000个航班计算&#xff0c;每天可节省约3万张纸、每年可节约1200多万元的费用成本。 小小一枚印章的转变&#xff0c;电子签章是如何做到的&#xff1f; 据了解&#xff0c;…

PowerCreatorCMS UploadResourcePic 任意文件上传漏洞复现

0x01 产品简介 PowerCreator CMS是翰博尔信息技术有限公司(简称翰博尔PowerCreator)推出的一款教育资源管理平台,专注于教育领域的信息化解决方案。PowerCreator CMS是集成了软件平台和硬件设备、多系统高度融合的教育资源管理平台。它旨在通过技术手段提升教育资源的管理、…

09视图操作

文章目录 视图概念/使用原因创建视图在单表上创建视图在多表上创建视图查看视图使用DESCRIBE | DESC语句查看视图基本信息使用SHOW TABLES语句查看视图基本信息使用 show create view/table 语句查看视图创建信息更新视图数据修改视图删除视图视图概念/使用原因 视图是从一个或…

七、golang基础之interface与类型断言

文章目录 一、接口&#xff08;Interface&#xff09;&#xff08;一&#xff09;概述&#xff08;二&#xff09;定义接口&#xff08;三&#xff09;实现接口&#xff08;四&#xff09;接口的使用 二、类型断言&#xff08;Type Assertion&#xff09;&#xff08;一&#x…