Rust 第四天—Rust进阶1

这篇具有很好参考价值的文章主要介绍了Rust 第四天—Rust进阶1。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

上一篇介绍了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顺利输出内容.

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

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

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

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

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

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

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

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

1.3 结构体的生命周期

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

1.4 定义结构体方法

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

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

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

已经多次提过生命周期在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);

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

所以本质上关联函数依旧是一种函数而不是结构体方法,并不直接作用于某一个结构体对象实例,使用时也需要用::语法来调用关联函数,最常见的应该就是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));
}

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

当然上面的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());
}

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

这个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));

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

我们定义了一个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());
}
​

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

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());

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

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

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

为了解决上面只能匹配某一个实现了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));
    }
}

Rust 第四天—Rust进阶1,Rust,rust,开发语言,后端

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

总结一下,通过trait我们可以定义很多特定了方法并且为每一种类型分别自定义这些trait的实现.但是Rust已经封装好了很多常用的trait,所以更多情况下,我们只需要#[derive(Trait)]就可以使用了.文章来源地址https://www.toymoban.com/news/detail-525270.html

到了这里,关于Rust 第四天—Rust进阶1的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【rust语言】rust多态实现方式

    学习rust当中遇到了这个问题,记录一下,不对地方望指正 多态是面向对象程序设计中的一个重要概念,指同一个行为或操作在不同实例上具有不同的行为或结果。简单来说,多态就是指同一种类型的对象,在不同的上下文中有不同的行为。多态性使得程序可以更加灵活、可

    2024年02月11日
    浏览(46)
  • 第五十四天学习记录:C语言进阶:动态内存管理Ⅱ

    1、对NULL指针的解引用操作 2、对动态开辟的内存的越界访问 3、对非动态开辟内存的free 4、使用free释放动态开辟内存的一部分 5、对同一块动态内存多次释放 6、动态开辟内存忘记释放(内存泄漏) 问:realloc的第一个参数的指针地址必须是malloc或calloc创建的在堆上的地址吗?

    2024年02月06日
    浏览(36)
  • C语言和Rust语言的互相调用(2)(Rust调用C)

    1.创建项目 rust调用c方式挺多的,这里采用最通俗易懂的方法,用构建脚本进行构建整个项目。 2.编辑build.rs的内容 这里的build.rs:若要创建构建脚本,我们只需在项目的根目录下添加一个 build.rs 文件即可。这样一来, Cargo 就会先编译和执行该构建脚本,然后再去构建整个项

    2024年02月02日
    浏览(49)
  • 【Rust 基础篇】Rust FFI:连接Rust与其他编程语言的桥梁

    Rust是一种以安全性和高效性著称的系统级编程语言,具有出色的性能和内存安全特性。然而,在现实世界中,我们很少有项目是完全用一种编程语言编写的。通常,我们需要在项目中使用多种编程语言,特别是在与现有代码库或底层系统交互时。为了实现跨语言的互操作性,

    2024年02月15日
    浏览(49)
  • Rust 笔记:Rust 语言中的常量与变量

    Rust 笔记 Rust 语言中的常量与变量 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343 邮箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/130875912 【介绍】:本文介绍 Rust 语言中的常量与变量。 上一节:《 上一节标题 》 | 下一节:《

    2024年02月06日
    浏览(59)
  • Rust 笔记:Rust 语言中的字符串

    Rust 笔记 Rust 语言中的字符串 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343 邮箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/130876665 【介绍】:本文介绍 Rust 语言中的字符和字符串的用法。 上一节:《 Rust 语言中使用 vector(向

    2024年02月06日
    浏览(51)
  • 【Rust】Rust学习 第十三章Rust 中的函数式语言功能:迭代器与闭包

    Rust 的设计灵感来源于很多现存的语言和技术。其中一个显著的影响就是  函数式编程 ( functional programming )。函数式编程风格通常包含将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。 更具体的,我们将要涉及: 闭包 ( Closures ),一个可以储

    2024年02月12日
    浏览(51)
  • Rust编程语言入门之Rust的面向对象编程特性

    Rust 受到多种编程范式的影响,包括面向对象 面向对象通常包含以下特性:命名对象、封装、继承 “设计模式四人帮”在《设计模型》中给面向对象的定义: 面向对象的程序由对象组成 对象包装了数据和操作这些数据的过程,这些过程通常被称作方法或操作 基于此定义:

    2023年04月21日
    浏览(51)
  • Rust语言从入门到入坑——(5)Rust 所有权

    主要介绍Rust所有权的知识,涉及到变量的作用域,内存释放机制,移动,克隆,引用等知识,很多知识是Rust语言特有机制。 所有权有以下三条规则: - Rust 中的每个值都有一个变量,称为其所有者。 - 一次只能有一个所有者。 - 当所有者不在程序运行范围时,该值将被删除

    2024年02月10日
    浏览(44)
  • rust入门系列之Rust介绍及开发环境搭建

    Rust基本介绍 网站: https://www.rust-lang.org/ rust是什么 开发rust语言的初衷是: 在软件发展速度跟不上硬件发展速度,无法在语言层面充分的利用硬件多核cpu不断提升的性能和 在系统界别软件开发上,C++出生比较早,内存管理容易出现安全问题的背景下。 为了解决开发系统界别软

    2024年02月12日
    浏览(64)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包