Rust 第四天—Rust进阶1

news2024/12/23 12:16:26

上一篇介绍了Rust的所有权特性,今天就把剩下一些之前没介绍但项目中常用的内容总结一下.

  • 结构体
  • 泛型
  • trait

1 结构体

和c语言一样,Rust使用struct关键字来定义一个结构体,结构体可以将不同的类型数据进行整合,加快内存访问速度.

1.1 结构体定义

struct Test{
    username:String,
    id:u64,
    alive:bool,
}

和c语言类似,我们可以像上面这样将不同的数据类型以及对应字段名封装为一个结构体.当然,和Rust的基本语法一样,这里的类型依然是后置的.

1.2 实例化结构体

let test1=Test{
        username:String::from("aaa"),
        id:1,
        alive:true,
    };

我们可以像上面这样直接对结构体中的字段进行赋值,从而创建一个结构体对象.当然也可以像下面这样写一个函数对结构体进行初始化,返回这个结构体就好.

fn new_test(username:String,id:u64)->Test{
    Test{
        username:username,
        id:id,
        alive:true,
    }
}
​
 let test2=new_test(String::from("bbb"),2);

为了能够顺利打印,我们在结构体定义上方加入一句#[derive(Debug)],这是为结构体添加一个attribute(中文不太好翻译,属性?)使得编译器能够自动为带有这个属性的结构体实现Debug trait.关于trait的内容之前提到过一些,等后期会慢慢总结.有了Debug trait,我们的结构体才能通过fmt::Debug顺利输出内容.

image-20230616013100352

当然,Rust的编译器也是足够聪明的.在我们写下new_test函数之后,编译器自动提示我们需要改进简化代码

image-20230616013224898

编译器提示可以使用初始化缩写,也就是说如果参数名与字段名相同,编译器是能够自动匹配的,这样可以简化为下图

image-20230616013423157

如果我们某个对象与其他对象大部分属性相同,仅仅有某几个属性不同,这时候也不用重新一个个属性去创建,直接使用现有对象属性值就好.

let test3=Test{
        id:3,
        ..test2
    };
println!("test3={:#?}",test3);

image-20230616013730100

1.3 结构体的生命周期

之前已经强调过,在Rust中出于内存安全设计很看重变量的生命周期,在结构体中也不例外.除了基础类型,其他类型如果被封装在结构体中,那么必须要考虑数据有效性与生命周期是否一致.当然,我们可以提前定义一个变量然后传入引用,从而延长生命周期.关于生命周期以及生命周期注解相关的内容,稍后会集中讲解.

1.4 定义结构体方法

上面我们已经实现了一个简单的结构体,但这还远远不够.我们希望能为这些结构体定义相应的结构体方法,从而使用结构体内部的成员实现某些功能,比如为上面的结构体实现一个自我介绍功能.

impl Test {
    fn introduce(&self)->String{
        String::from("My name is:")+ &self.username
    }
}
println!("{}",test3.introduce())

image-20230616142505113

已经多次提过生命周期在Rust中的重要性,这里还得再次提醒.为了约束定义的结构体方法在结构体上下文中,所以结构体方法的声明都在impl包起来的块中.在方法实现时,我们也可以直接传入&self来代替一个明确的结构体对象或指针,因为方法就在上下文中编译器可以自己推断类型.这里&表示方法可以拥有结构体的所有权,当然默认依然是不可变借用.

另外一个值得注意的点,不是所有在impl中定义的都是结构体方法,如果不带有&self作为参数,那这些函数称为关联函数.

impl Test{
    fn from(username:String)->Test{
        Test{username, id: 0, alive: false }
    }
}
let test4=Test::from(String::from("ccc"));
println!("{:#?}",test4);

image-20230616170336316

所以本质上关联函数依旧是一种函数而不是结构体方法,并不直接作用于某一个结构体对象实例,使用时也需要用::语法来调用关联函数,最常见的应该就是String::from().

2 泛型

对于强类型语言来说,我们在声明某个变量时必须设置对应的数据类型(或者依靠编译器推断).在之前Go(1.18之前)就饱受不支持泛型的困扰,为了要支持不同的传入数据类型就得重复声明同样逻辑的函数.我学Go的时候还是大二,那个时候技术能力不够也就体会不到不支持泛型的痛苦,越到后面写起一些项目同样的逻辑函数仅仅为了支持不同类型就得重复,好在去年Go1.18支持泛型并且整体使用体验还不错.

说回正题,泛型函数往往在定义时不知道传入的类型实参,只有编译调用的时候才能被真正定义传入类型,然后将泛型定义替换为对应的具体类型.

2.1 泛型函数

最常见的应该就是泛型函数,将相同处理逻辑的函数适应不同的输入类型,如下面的demo

fn max_number<T: std::cmp::PartialOrd>(a:T,b:T)->T{
    return if a > b {
        a
    } else {
        b
    }
}
​
​
fn add<T: std::ops::Add<Output=T>>(a:T,b:T)->T{
    a+b
}
​
​
fn main(){
    println!("{} + {} = {}",1,2,add(1,2));
    println!("{} + {} = {}",3.14,9.99,add(3.14,9.99));
​
​
    println!("{} and {},max is {}",1,2,max_number(1,2));
    println!("{} and {},max is {}",10.3,2.2,max_number(10.3,2.2));
}

image-20230617101417544

当然上面的demo也说明了泛型并不是随意的.比如求最大值就需要输入的类型能进行比较,也就是说对输入类型进行类型限制,而在Rust中添加类型限制需要一些trait辅助,关于trait的内容别急会在下面总结.

2.2 泛型结构体

不止普通函数,结构体的字段类型也可以用泛型定义.比如我们创建一个结构体代表坐标点,就无须纠结坐标到底属于整型还是浮点(当然用浮点数往往更通用)

#[derive(Debug)]
struct Point<T>{
    x:T,
    y:T,
}
​
impl<T>Point<T>{
    fn swap(&mut self) -> &mut Point<T> {
        std::mem::swap(&mut self.x,&mut self.y);
        return self;
    }
}
​
fn main(){
    let mut point1 =Point{x:1,y:3};
    let mut point2 =Point{x:3.4,y:1.2};
    let point3=Point{x:1.33,y:1.22};
​
    println!("point1={:#?},point2={:#?},point3={:#?}",point1,point2,point3);
​
    println!("swap point1={:#?},swap point2={:#?}",point1.swap(),point2.swap());
}

image-20230617121406209

这个demo里首先创建了一个泛型结构体Point,里面x,y分别代表横纵坐标.此外,我们还为结构体实现了一个swap方法,用来交换坐标值.但是仍然不满足,我还想实现计算两点之间距离的方法,于是有了下面的demo

trait Distance<T>{
    fn distance(&self,other:&T)->f64;
}
​
​
impl<T> Distance<Point<T>> for Point<T>
where
  T:Sub<Output=T>+Mul<Output=T>+Add<Output=T>+Into<f64>+Copy,
{
    fn distance(&self, other: &Point<T>) -> f64 {
        let dx=self.x-other.x;
        let dy=self.y-other.y;
        let dis=dx*dx+dy*dy;
        return dis.into().sqrt();
    }
}
​
println!("distance of point2 and point3={}",point2.distance(&point3));

image-20230617134103712

我们定义了一个trait实现distance方法去计算距离,然后将这个trait指定结构体去具体实现.

Rust不仅泛型支持良好,更令人激动的是使用泛型几乎不会影响性能.因为在编译过程中,编译器会将泛型代码单态化(填入具体类型),因此在运行时不会有任何虚函数调用影响性能.

3 trait

之前的很多demo都使用到了trait,在这个章节就正式来解释一下Rust里常常出现的trait到底是什么.

如果了解其他语言,一定听过接口这个词.那其实trait就是Rust中的接口,只不过相比起普通意义上的接口,Rust中的trait更加灵活并且可以设置默认方法,下面用一个demo来看看trait的常见使用.

3.1 定义trait

trait Callable{
    fn call(&self)->String;
}

这里定义了一个Callabletrait,其中方法签名call要求返回一个String.

3.2 为结构体实现trait

struct Person{
    name:String,
    age:u8,
    sex:String,
}
​
​
impl Callable for Person{
    fn call(&self) -> String {
        format!("my name is {},age:{},sex:{}",self.name,self.age,self.sex)
    }
}
​
​
fn main(){
    let person=Person{name:String::from("aa"),age:18,sex:String::from("male")};
    println!("{}",person.call());
}
​

image-20230618111610450

3.3 trait bound

在定义并实现了trait之后,我们也可以将trait作为参数.特别是在泛型编程中,我们可以通过trait约束传入的类型参数

fn Test<T:Callable>(item:&T){}

并且在上面泛型的demo中也展现了trait是“可加的”,如果需要多种约束,只需要在尖括号中<T:trait1+trait2>.当然如果对不同参数需要进行不同约束,也可以用where分别定义trait,这个就和上面demo一样了.

3.4 trait作为返回值

我们可以通过impl trait作为返回值说明函数返回了某个类型且该类型实现了这个trait,不过这种方法返回只能有一个具体类型.

fn return_callable()->impl Callable{
    Person{
        name:String::from("call"),
        age:11,
        sex:String::from("female"),
    }
}
let call_person=return_callable();
println!("{:#?}",call_person.call());

image-20230618124447901

struct Dog{
    name:String,
}
​
​
impl Callable for Dog{
    fn call(&self) -> String {
        format!("I am a dog,my name is {}",self.name)
    }
}
​
fn return_bad_callable(flag:bool)->impl Callable{
    if flag{
        Person{
            name:String::from("call"),
            age:11,
            sex:String::from("female"),
        }
    }else{
        Dog{
            name:String::from("Wang")
        }
    }
}

image-20230618212050129

为了解决上面只能匹配某一个实现了trait类型的问题,我们可以用到特征对象.特征对象用dyn关键字进行类型声明,对比起动态语言中的鸭子类型,Rust这种特征对象没有运行时的检查或者异常,在编译期就能够保证所有特征对象都实现了所定义的trait,否则也不能够顺利编译.

fn create(x:&dyn Callable) -> String {
    x.call()
}
​
​
fn return_various_call(flag:bool){
    if flag{
        let tmp_call=Person{
            name:String::from("tmp_call"),
            age:11,
            sex:String::from("female"),
        };
        println!("{}",create(&tmp_call));
    }else{
        let tmp_call=Dog{
            name:String::from("tmp_Wang")
        };
        println!("{}",create(&tmp_call));
    }
}

image-20230620162338254

像这样我们使用特征对象来构建条件选择,只要我们实现了相应的特征就可以包装为特征对象进行使用.使用特征对象也就意味着是动态分发的,也就是只有运行时才知道具体对应的调用类型.

总结一下,通过trait我们可以定义很多特定了方法并且为每一种类型分别自定义这些trait的实现.但是Rust已经封装好了很多常用的trait,所以更多情况下,我们只需要#[derive(Trait)]就可以使用了.

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

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

相关文章

【第四天学习】决策分支,判断语录

决策分支 If语句 If语句是对基础的一种选择结构语句&#xff0c;它主要有三种形式&#xff0c;分别是if语句&#xff0c;if else语句和if else If else多分支语句。 int nCheck 0; if(nNum4) { nCheck4; } else { nCheck8; }If语句其实就是一个判断语句&#xff0c;它会判断…

详细解释lvs的工作原理

vsl用于集群中的直接路由它的原理如下 如果在公司并发太高了怎么解决 1.加配置cpu 内存 带宽 ssd高效硬盘 2.加服务器 为用户提供服务 横向扩展 集群是什么 由的多台主机构成,相当于一台大型计算机,只提供一个访问入口(域名与ip地址) 集群用在那个场景 高并发场景 vrrp是…

linux下删除ARP缓存表【网络工程】(保姆级图文)

目录 linux下删除ARP缓存表总结 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 温馨提示&#xff1a;对虚拟机做任何设置&#xff0c;建议都要先快照备份&#xff01; linux下删除ARP缓存表 这里老师的命令ip…

docker的安装和使用

1.新建一个项目 比如vue init vuelatest完事之后运行打包到build目录下 2.在项目根目录下通过执行命令 touch Dockerfile 3.拉取nginx镜像 首先打开你的Docker&#xff0c;默认会启动。控制台拉取 Nginx 镜像&#xff1a;运行 docker pull nginx4.在根目录创建Nginx配置文件…

【多线程】锁策略

1. 说在前面 这里的锁策略内容&#xff0c;属于典型的面试八股文&#xff01;如果未来工作&#xff0c;需要实现一把锁&#xff0c;那么得好好研究下锁策略&#xff0c;但基本上不会让我们自己设计一把锁的。 而这里的锁策略内容不局限于 Java&#xff0c;任何 "锁" …

python接口自动化(十七)--Json 数据处理---一次爬坑记(详解)

简介 有些 post 的请求参数是 json 格式的&#xff0c;这个前面发送post 请求里面提到过&#xff0c;需要导入 json模块处理。现在企业公司一般常见的接口因为json数据容易处理&#xff0c;所以绝大多数返回数据也是 json 格式的&#xff0c;我们在做判断时候&#xff0c;往往只…

golang 实现四层负载均衡

大家好&#xff0c;我是蓝胖子&#xff0c;做开发的同学应该经常听到过负载均衡的概念&#xff0c;今天我们就来实现一个乞丐版的四层负载均衡&#xff0c;并用它对mysql进行负载均衡测试&#xff0c;通过本篇你可以了解到零拷贝的应用&#xff0c;四层负载均衡的本质以及实践。…

代码模版-element plus如何进行前端校验输入框

文章目录 步骤一&#xff1a;引入 element plus 框架步骤二&#xff1a;使用 element plus 的 form步骤三&#xff1a;form 明确指定 rules步骤四&#xff1a;事件触发校验 使用 vue3 element plus 步骤一&#xff1a;引入 element plus 框架 先 npm 安装 在 src/main.js 中…

linux[armbian]环境安装nginx

文章目录 linux[armbian]环境安装常用命令遇到的问题和解决方法问题一&#xff1a;conf/koi-win复制错误解决方法问题二&#xff1a;缺少相关的日志目录解决方法 linux[armbian]环境安装 下载Nginx&#xff1a; 访问[Nginx官方网站]&#xff08;https://nginx.org/)&#xff0c…

排序算法(1):冒泡排序

在计算机科学领域&#xff0c;排序算法是一个重要的主题。冒泡排序法是最基础且简单的排序算法之一&#xff0c;它的原理简单易懂&#xff0c;是学习排序算法的理想起点。本文将详细介绍冒泡排序法的原理、实现方法以及优化技巧&#xff0c;帮助读者全面了解和掌握这一经典算法…

重新安装conda时报错

自己因为最近利用 conda 安装了比较多的软件,在输入创建环境时&#xff1a; conda create -n python27 python2.7 报错&#xff0c;环境创建不成功。 显示 miniconda3 文件夹已存在 &#xff08;因为安装时会默认安装至此目录&#xff0c;如果此目录已经存在则会冲突报错&…

【基础算法】贪心算法

贪心算法又称贪婪算法&#xff0c;是一种常见的算法思想。贪心算法的优点是效率高&#xff0c;实现较为简单&#xff0c;缺点是可能得不到最优解。 贪心算法的基本思想 贪心算法就是在求解问题时&#xff0c;总是做出当前看来最好的选择。也就是说贪心算法并不从整体最优上考…

word转PDF后图片为何会变小?怎么解决?

有些同学反映将Word文档转换为PDF后&#xff0c;发现里面的图片居然变小了&#xff0c;这是什么原因造成的&#xff1f;该怎么解决呢&#xff1f; 先来说说原因&#xff0c;我个人认为可能是由以下原因造成的&#xff1a; 1、word插入图片后压缩“太狠”了。当你在word中插入…

GENMARK控制器维修S08S4P.D工业电脑维修

机器人GENMARK SYSTEM CONTROLLER系统控制器维修S08S4P.D工业电脑&#xff1b;晶圆转移机器人SΛΛALL CONTROLLER&#xff1b; SΛΛC1100 半导体设备机械臂GENMARK控制器等 GenMark的两大构架&#xff1a;eSensor&#xff08;电子传感&#xff09;和Elecitrowetting&#xf…

ibaq intensity 蛋白组学 蛋白质组学两个定量方法(iBAQ和LFQ)的区别及常见的标准化方法

4.MaxQuant中的Intensity&#xff0c;LFQ和iBAQ 大佬的软件&#xff0c;三种定量算法都发了文章。 Intensity是将某Protein Groups里面的所有Unique和Razor peptides的信号强度加起来&#xff0c;作为一个原始强度值。用得很少。iBAQ是在Intenstiy的基础上&#xff0c;将原始…

JUC#线程池加锁逻辑梳理

带着问题看源码 为什么要用线程池?Java是实现和管理线程池有哪些方式? 请简单举例如何使用。为什么很多公司不允许使用Executors去创建线程池? 那么推荐怎么使用呢?ThreadPoolExecutor有哪些核心的配置参数? 请简要说明ThreadPoolExecutor可以创建的是哪三种线程池呢?当…

【Web3】Web3Js高频Api

目录 Web3Js方法 初始化Web3实例 Web3Api 创建账号Api 获取余额Api 单位转换工具函数 Web3Js方法 web3.eth&#xff1a;用于与以太坊区块链和智能合约之间的交互。 web3.utils&#xff1a;包含一些辅助方法。 web3.shh&#xff1a;用于协议进行通信的P2P和广播。 web3…

1066 Root of AVL Tree (PAT甲级)

这道题类似1123题。 #include <cstdio> #include <algorithm>struct node{int key;node* left nullptr;node* right nullptr; };int N, t; node* root nullptr;int getHeight(node* r){if(!r){return 0;}return std::max(getHeight(r->left), getHeight(r-&…

【json-server】json-server安装与使用:

文章目录 一、下载安装:二、启动db.json数据及相关参数&#xff1a;三、创建json数据——db.json&#xff1a;四、修改端口号&#xff1a;五、操作数据&#xff1a;【1】常规获取&#xff1a;【2】过滤获取 Filter:【3】分页 Paginate&#xff1a;【4】排序 Sort&#xff1a;【…

使用 .editorconfig 文件来统一编程风格

做过长期开发的程序员都知道保持编程风格统一的重要性, 统一的风格能够降低各种成本. 有一句名言是咋说的来着? 代码主要是给人看的, 其次才是给电脑去运行. 但另一方面, 大家又普遍是偷懒的, 对于这些长期会受益, 但短期收益不明显甚至带来麻烦的事, 许多团队中的成员不能说抵…