锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

8万字带你入门Rust

时间:2023-05-06 10:07:00 a1101a1g压力变送器压力变送器std920y522差压变送器s2压力变送器器cyb11w微压力变送器5压力变送器cyb

Rust

?? 学习建议:

  • 先从 从整体出发,不要让自己陷入细节
  • 与你已知的知识建立联系
  • rust 和go一样采用 实现代码复用的组合手段,不要深思为什么不继承
  • 学会阅读源码,从源码中学习
  • Rust设计哲学

使用 cargo new 项目名

在终端中构建项目

使用 cargo build 建设和运营项目

也可以使用 cargo run 完成编译和操作任务

第一个程序:

fn main(){     println!("hello world!"); } 

Rust默认情况下,中变量是不可变的的变量是不可变的mut修改变量的关键字可以改变。

用let创建变量:

let foo = 5;//foo是不可变的 let mut bar = 5; //bar是可变的 

& 默认情况下引用也是不可变的

&mut guess ///声明可变引用  &guess ///声明不可变的引用 

示例:

use std::io; // 导入包 fn main() { println!("Guess the number!"); //输出 println!("Please input your guess:"); let mut guess =String::new()//声明可变变量并绑定空白字符串 io::stdin().read_line(&mut guess).expect("Failed to read line");//从键盘获取输入 //read_line它读取时会返回一个值 io::Result值(它个枚举类型),它有OK和ERR如果读取失败,两个变体将返回expect并输出内容,未编写expect函数会发出警告 println!("You guessed: {}", guess); // 这里的花括号是占位符,用几个花括号打印几个值{} let x=50; let mut y = 100; println!("You guessed: {}", guess); // 这里的花括号是占位符,用几个花括号打印几个值{} let x=50; let mut y = 100; println!("x= {}, y = {}",x,y);//多个输出 println!("game over!"); }   use std::io;  fn main(){  println!("猜数!");  println!("猜一个数字:");  let mut guess=String::new()//创建空白字符串并绑定到变量guess  io::stdin().read_line(&mut guess).expect("不能读行!"); ///下行等同于上行,不使用 use 如果导入,需要使用以下方法 // std::io::stdin().read_line(&mut guess).expect("不能读行!"); println!("你猜的数字是:{}",guess);  }    # 使用rand包需要在cargo.toml文件中将rand包声明是依赖 rand="0.3.14"   # 添加后使用 cargo build 重建这个项目 

升级依赖包使用 cargo update 依赖包的命令升级

// 猜数游戏示例 use std::io; use rand::Rng;//导入随机数模块  trait use std::cmp::Ordering; fn main() { println!("Guess the number!");  let numrand=rand::thread_rng().gen_range(1,101)  println!("Please input your guess:");  let mut guess =String::new();  io::stdin().read_line(&mut guess).expect("Failed to read line"); //rust 允许使用同名新变量隐藏旧变量的值 ///从这一行开始,这个guess不是上面的变量,而是第二个guess原来上面的变量 //用户要输入过程中按的回车键,会导入我们的输入字符串额外多出一个换行符,所以使用 trim()去除函数 //trim()去除字符串前后的空间 //parse()该方法将字符串分析为数值类型 let guess:u32=guess.trim().parse().expect("plasce type a number!");  println!");  println!("You guessed: {}", guess);  println!("随机数字为:{}",numrand); match guess.cmp(&numrand)谁匹配谁就执行谁 Ordering::Less => println!("too small!"), Ordering::Greater => println!("to big!"), Ordering::Equal => println!("you win!"), }  println!("game over!");  } 

上面的代码中有一个概念叫做 隐藏(shadow):

rust 允许使用同名新变量隐藏旧变量的值

利用循环实现多次猜测

use std::io; use rand::Rng; use std::cmp::Ordering; fn main() { println!("Guess the number!");  let numrand=rand::thread_rng().gen_range(1,101)  loop{///循环起始位置 println!("Please input your guess:"); let mut guess =String::new();  io::stdin().read_line(&mut guess).expect("Failed to read line");  let guess:u32=match guess.trim().parse(){ Ok(numrand) => numrand, Err(_) => continue,///不需要错误的信息,可以下划线忽略 };//这里把 expect方法换成了match表达式  println!("You guessed: {}", guess);  println!("You guessed: {}", guess);  println!("随机数字为:{}",numrand);  match guess.cmp(&numrand){ Ordering::Less => println!("too small!"), Ordering::Greater => println!("to big!"), Ordering::Equal => {     println!("you win!");     break;//猜对了就退出 } } } } 

这里把 expect方法换成了match表达式

let guess:u32=match guess.trim().parse(){ Ok(numrand) => numrand, Err(_) => continue,///不需要错误的信息,可以下划线忽略 }; 

continue用法和C 、go、java等语言一样!

Rust保留关键字

关键字

描述

as

实施基本类型转换,消除指定的项目 trait 的歧义,在 use 与 extern crate 语句重命名条目

break

立即退出一个循环

const

定义常量或不可变指针

continue

继续下一个循环迭代

crate

连接外包或代表当前包的宏变量

dyn

表示 trait 对象可以动态分发

else

if 和 if let 回退分支控制结构

enum

定义一枚

extren

连接外包、函数、变量

false

布尔值假字面量

fn

定义函数或函数指针类型

for

迭代元素实现了迭代 trait,指定高级生命周期

if

基于条件表达式的分支

impl

实现类型自身的功能或 trait 定义的功能

in

for循环语法的一部分

let

绑定变量

loop

无条件循环

match

用模式匹配一个值

mod

定义模块

move

封闭包获得所有捕获变量的所有权

mut

声明引用、裸指针或模式绑定的可变性

pub

声明结构体字段,impl块或模块的公共性

ref

通过引用绑定

return

从函数中返回

Self

指代正在其上实现 trait 的类型别外 S是大写的=

self

指代方法本身或者当前模块 s是小写的

staticc

全局变量或者持续整个程序执行过程的生命周期

struct

定义一个结构体

super

当前模块的父模块

trait

定义一个 trait

true

字面量布尔真

type

定义一个类型别外或关联类型

unsafe

声明不安全的代码、函数、trait或实现

use

把符号引入作用域中

where

声明一个用于约束类型的 从句

while

基于一个表达式结果的条件循环

未来可能会使用的保留关键字:

abstract

async

become

box

do

final

macro

override

priv

try

typeof

unsized

virtual

yield

通用编程概念

fn main(){
println!("hello world!");
//默认不可变
let mut x = 5;

println!("x= {}",x);

x=10;

println!("x= {}",x);

//常量:它不可以使用mut关键字,常量永远都是不可变的
//声明一个常量使用const关键字
//常量只可以绑定到常量表达式
//RUST中常量 一般使用大写字母
const MAX_POINTS:u32=1000;


//Shadowing
let x = 5;
println!("{}",&x);//5
let x =x+1;
println!("{}",&x);//6
let x =x+2;
println!("{}",&x);//8

let spaces_str="     ";
let spaces_num=spaces_str.len();
println!("{}",spaces_num);//5

使用 const来定义一个常量,不能使用let关键字来定义常量;

不能使用 mut 关键字修饰一个常量,常量总是不变的!

在 rust 中,变量名一般都有大写!

隐藏机制不同于将变量声明为 mut 的 !
重复使用 let 关键字会创建出新的变量,因此可以复用的时候改变它的类型!

rust数据类型

标量类型和复合类型

注意:Rust是一门静态类型语言,这意味着它在编译程序的过程中需要知道所有变量的具体类型。

标量类型:单个值类型的统称; 4种:整数、浮点数、布尔值、字符

/*标量类型:整数,浮点数(f32,f64默认的),布尔值,字符(char),字符串,元组,枚举
复合类型:数组,结构体,指针,元组,枚举
u32:无符号整数类型,占32位空间
u8,u16,u32,u64,u128 无符号
i8,i16,i32,i64,i128 有符号
无符号以U开头,有符号以I开头
整数默认类型是i32
*/
//isize 和 usize 这两种是由运算程序的计算机硬件决定的
let guess:u32 = "42".parse().expect("Not a number!");
println!("{}",guess);
//rust声明的变量没有使用会有警告
let x:f32 = 3.0;
let x:f64 = 4.0;
let b:bool = true;
let b:bool =false;
let x='z';
let y:char ='y';
let z='??';//也可以存放这种

整数类型有: (它们占的空间大小也就是后面对应的数字单位为bit)

  • 无符号:u8,u16,u32,u64,usize
  • 有符号:i8,i16,i32,i64,isize

usize和isize:取决于程序运行的目标平台;在64位架构上就是64bit,而32位架构上就是 32bit

rust默认的整数字面量是:i32

浮点数类型:

  • f32
  • f64 (默认)

复合类型:可以将多个不同类型的值组合为一个类型;2种:元组(tuple) 数组 (array)

//复合类型
//Tuple类型可以将多个类型的值放在一个类型里面,和C++中的元组类似
//tuple的长度的固定,一旦创建就不能改变
//如果不明确是什么类型,可以使用_来代替
let tup: (i32,char,bool)=(15,'a',true);
let tup:(_,_,bool)=(3.14,"boy",false);
let tup=(50,1.25,1);//也可以使用模式匹配
let ont=tup.0;
let two=tup.1;//也可以通过点来进行访问
let (x,y,z)=tup;
println!("{},{},{}",x,y,z);//获取tup的值 解构:将元组拆解为n个不同的部分
//数组
//数组和C++中的差不多
//长度也是固定的
// Vertor更加灵活,长度可以改变
//和数组类似,不确定使用哪个,就使用 Vector
let arr=[1,2,3,4,5,6,7,8,9];
let avec=vec![1,2,3,4,5,6,7,8,9];
println!("{}",avec[0]);
println!("{}",arr[5]);
//另外一声明数组的方法
let a=[3;5]; //创建数组并且初始化为5个3

元组和数组都拥有固定的长度!

元组用小括号();数组用中括号 []

有一种动态数组类型:vector

函数

rust使用蛇形命名法(只使用小写字母,使用下划线分隔单词)来规范函数和变量名称的风格!

// 函数调用 
add_function();
add(4,56);

let y=5+6;

let y={//表达式
    let x=3;
    x+1 //这个加上了分号就变成了语句;这个相当于返回值
};

let n1={
    let u=6+5;
    u
};


println!("{}",y);//4

=================================================
//函数和注释
// 声明函数使用  fn 关键字 : go语言使用 func 关键字
// 规范是函数名称使用小写,单词之间使用_分割
fn add_function(){
println!("hello function");
}

fn add(x:u32 , y: u32){//rust必须指定函数参数类型
println!("x={}",x);
println!("y={}",y);
}
// 函数的返回值
fn add1(x:u32 , y:u32) ->u32 {//在参数括号后面加上->类型 就是返回
let x=x+y;
x   //返回语句不能有分号,有了分号就变成了语句
}

参数和参数类型之间使用 : 分隔!

rust把语句和表达式区分为两个不同的概念:

  • **语句:**执行操作但不会返回值的指令
  • **表达式:**会进行计算并且产生一个值作为结果的 指令

记住:语句不会有返回值

表达式加上分号就会变成了语句。

rust函数的返回值使用 -> 返回值类型;如果是多个就是小括号括起来:-> (类型1, 类型2 ….)

fn five() -> i32 {
    5  //这样也是对的返回5
    //如果不使用这种方式返回,也可以使用 return,使用这个需要加分号 : return 5;
}
=================================================

fn five() -> i32 {
    5; //错的!!!不能加分号
}

rust注释

// 单行注释

/**/ 多行注释

控制流

if 和 else

示例:

// 控制表达式  if else
//这个和go,python语言差不多
let x=5;

if x<10 {
    println!("你的数字真小!");
}else if x>10&&x<90{
    println!("你的数字在10-90之间!");
}else{
    println!("你的数字真大!");
}
let y=true;//这里y的值不能为0或者1不然会报错!!!
if y {
    println!("{}",y);
}

rust不会自动尝试将非布尔类型的值转换为布尔类型!!!,所以上面代码中y的值只能为布尔值。

过多的else if 表达式应该用 match 替代!!!

let b=10;
//if是一个表达式,可以let语句右侧使用它来生成一个值
// 也可以这样判断实现像 ? : 相同的功能
let number=if b>5 {100} else { 900 }; //if和else里面的类型要一样,静态编译型语言

//else if 太多了,可以使用match来重构
let number=100;
match number{//这规则和case差不多,也可以使用下划线_
    1=>println!("one"),
    2=>println!("two"),
    3=>println!("three"),
    _=>println!("other"),
}

所有 if 分支里面可能返回的值都必须是一种类型的

let nu=if 4>5{
    54-12
}else{ -900+65};//像这样也是可以的,记住不要里面加分号
println!("nu={}",nu);

rust循环结构

rust提供了 3 种循环结构:loop 、while、for。

loop :反复执行一块代码,直到条件满足(break)或者我们强制退出!!

loop {
    ...
    if 条件 {
        
    }
}


// loop  不会像 do while必定会执行一次,其他和它一样,如果条件放在最前面,一开始就不成立,就不会执行
let mut count=0;
let res = loop{
   count+=1;
    println!("725");
    if count==10{
        break count;
    }
};//这里分号别忘记写了


let mut con1=0;
let res=loop{
    con1+=1;
    if con1 == 10 {
       break con1*2 //这里不加分号
    }
};
println!("con1={}",res);//20
let mut con2=0;
let res=loop{
    con2+=1;
    if con2 == 10 {
       break con2*2; // 这里加上分号
    }
};
println!("con2={}",res);//20
//上面两种方式是等价的

while 用法和其他语言一样

// while
let arr=[1,2,3,4,5,6,7,8,9];
let mut le=arr.len();//数组长度比数组下标大1
while le>0{
    le=le-1;
   println!("{}",arr[le]); 
}

for 循环:推荐使用简洁又高效;rust最为常用

// for
// 使用for循环又安全又高效
let arr=[1,2,3,4,5,6,7,8,9];
for a in arr.iter(){
println!("{}",a);
}
===============================================
语法: 
for 变量名 

Range:用来生成数字序列!

// Range 标准库提供
// 指定一个开始数字和一个结束数字,它可以生成它们之间的数字(左闭右开)
// rev方法可以反转 Range
for number in(1..10).rev(){//小括号数字中间是两个点
    println!("{}",number);
}

所有权

// 所有权是Rust最独特的特性核心特性
// 内存是通过所有权系统来管理的
//堆和栈是代码在运行时可以傅 的内存空间
// stack 栈  这上面的数据必须拥有固定的大小 
//  heap 堆  编译时大小未知或者大小可能发生变化的数据必须存放在 heap中
// 访问heap中的数据要比访问stack中的数据慢,多了次指针跳转

所有权是Rust最独特的特性核心特性

所有权规则:

  1. rust中的每一个值都有一个对应的变量作为它的所有者;
  2. 在同一时间内,值有且仅有一个所有者;
  3. 当所有者离开自己的作用域时, 它持有的值就会被释放掉;

作用域:一个对象在程序中有有效范围;

rust 中可以用大括号 {} 表示一个作用域,或者隔离一个作用域!!!

String类型

字符串字面量是不可变的;

let s="hello world!"; //不可变的 分配在栈上的

为了方便操作rust提供了第二种String类型:这个类型会在====上分配自己需要的存储空间:调用 from 函数来创建 String 实例

let s=String::from("hello");
//在堆上分配的,是可变的

区别:字符串字面量是分配在栈上的不可变,而String是分配堆上的是可变的!!!

内存布局:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XsJoFVJ1-1651467632358)(E:sysyPictures哔哩哔哩动画image-20220326202623950.png)]

注意图中 String 类型的分配方式;

内存与分配

两个关键概念:

  • rust 在变量离开作用域的时候,会调用一个叫作 drop的特殊函数
  • rust会在作用域结束的地方自动调用 drop 函数

在C++中这种对象生命周期结束时释放资源的模式也称为资源获取即初始化(RAII)

变量和数据交互的方式:移动 Move

// 变量和数据交的方式:移动(Move)
//多个变量可以与同一个数据使用独特的方式来交互
let s1=String::from("shenyang");
let s2=s1;//在这里这样,rust会废弃s1的所有权,s1的值被移动到s2中,s1的值被清空
//println!("{}",s1);//这里使用报错,因为s1已经被废弃了
/*let s1="shenyang";
let s2=s1;
像这样就可以,不会报错!!!
*/
println!("{}",s2);
// 一个String 由3部分组成:
// 一个指针,len(长度),cap(容量)分配在栈上,而字符串的内容被分配在堆上

内存布局:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JO54qY2f-1651467632359)(E:sysyPictures哔哩哔哩动画image-20220326203349038.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfZlmpuw-1651467632359)(E:sysyPictures哔哩哔哩动画image-20220327094827868.png)]

上面把 s1 的值赋给 s2 的时候只复制了它在存储在栈上的指针、长度及容量字段

需要注意的是它没有复制指针指向的堆上数据!

引出问题:s1 和 s2 离开作用的时候会尝试去重复释放相同的内存,导致二次释放

rust解决方案: rust在这种情况下会将 s1 废弃,不再视为一个有效的变量,s1 离开作用域后也不需要清理任何东西!!!

浅拷贝和深拷贝

C++中的深浅拷贝:

  • 深拷贝:在堆区重新申请空间进行拷贝操作、拷贝完整的内容
  • 浅拷贝:只拷贝地址,也就是编译器本身提供的拷贝构造函数做的浅拷贝操作

浅拷贝带来的问题:堆区的内存重复释放以及内存泄漏

有堆区开辟的属性,一定要提供拷贝构造函数防止浅拷贝带来的问题。

rust拷贝s1到s2的方式就可以视为浅拷贝。

术语: 移动(MOVE)

rust中应该是 s1 被移动到 s2 中。因为 s1 会被废弃了!!

一个设计原则:rust 永远不会自动地创建数据的深拷贝。所以在 rust中,任何自动的赋值操作都可以视为高效的。

需要用到深拷贝就是克隆(clone)

变量和数据交互的方式: 克隆 Clone

当要做深拷贝操作的时候,rust提供一个方法: Clone()

// clone 克隆 比较消耗资源
let a1=String::from("hello");
let a2=a1.clone();//克隆作了深度拷贝操作
println!("{},{}",a1,a2);//这里a1变量就没有被废弃,因为是直接把a1克隆给a2
// 和上面的作对比

Clone()方法复制栈上数据的同时,也复制了堆上的数据!!!

克隆有个缺点:就是比较消耗资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNe07bTL-1651467632360)(E:sysyPictures哔哩哔哩动画image-20220326203136545.png)]

重点:如果一个类型拥有了 Copy 这种 trait ,那么它的变量可以在赋值给其他变量之后仍然保持可用性。

如果一个类型本身或者这种类型的任意成员实现了 Drop 这种 trait ,那么rust 就不允许它实现 Copy 这种 trait了。

/*
stace上的数据:复制
Copy trait,可以用于完全存放在栈上的类型
如果一个类型实现Copy trait,那么旧的变量在赋值后仍然可以使用
一些拥有Copy trait的类型:
任何简单标量的组合类型都可以是Copy的;任何需要分配内存或者某种资源的都不是Copy的
拥有的:bool char 所有的浮点类型,所有的整数类型
tuple(元组)前提是其中所有的字段都是Copy的 eg:
(i32,i32)是
(i32,String)不是
*/

任何简单标量的组合类型都可以是Copy的;任何需要分配内存或者某种资源的都不是Copy的

所有权与函数

理解这里:要理解了上面的内容比如:复制操作、克隆操作、 Copy 、Drop

fn main(){
 // 所有权与函数
// 函数在返回值的过程中也会发生所有权的转移
let s1=gives_ownership();

let s2=String::from("hello");

let s3=takes_and_gives_back(s2);//s2的所有权被移动到函数里面,从这里开始 s2 不再有效

 let s4=100;//由于 i32 类型是 Copy 的,我们在这里之后还可以继续使用 s4
    
/*
一个变量离开作用域时会被Drop函数还回,除非它的所有权被转移另外一个变量上
*/
    
}

fn gives_ownership() -> String{
let some_string=String::from("hello");
some_string //这个的所有权移动到调用它的上面也就是上面的s1上
}

fn takes_and_gives_back(a_string:String)->String{//s2的所有权被移动到函数参数上面
    a_string //这个作为返回值的所有权移动到调用它的上面也就是上面s3上面
}

fn makes_copy(x:i32) {
    println!("x={}",x);//x在这里离开作用域并不会有什么特别的事发生就是正常的消亡
}

上面的函数中的返回值是移动操作不是返回所有权操作 !!!参数传递进去函数的时候,函数会获得所有权

返回值和作用域

??

遵循模式:将一个值赋值给另外一个变量时就会发生所有权转移,当一个持有堆数据的变量离开作用域时,它的数据就会被 Drop 清理回收,除非数据的所有权被移动到了另一个变量上 面。

函数在返回值的过程中也会发生所有权的转移!!!

问题:当希望调用函数的时候保留参数的所有权,就要将传入的值作为结果返回,但同时函数也可能会需要返回自己的结果。

??:采用元组解决:太过于繁琐

fn main(){
    
    let s1=String::from("hello");
    
    let (s2,len)=calculate_length(s1);
    //接收多个参数的时候,需要忽略某个参数可以下划线 
    println!("{},{}",s1,len);
}

fn calculate_length1(s:String) ->(String,usize) {
   let length= s.len();//取得所有权
   (s,length)//采用元组解决同时返回多个值
}

:采用元组可以让函数同时返回多个值!!!

引用与借用

?? 解决上面采用元组返回太过于繁琐的问题

问题:我们想要调用函数的时候,不转移值的所有权

??:& 代表引用的含义,可以在不获取所有权的情况下使用值。

* 代表解引用

& 参数类型 不可变引用(默认的)

& mut 参数类型 可变引用(调用时的参数也要是可变)

fn main(){
 let mut s1=String::from("hello");
    
//& 表示引用,允许使用值并且不取得所有权  对应解引用 * 
//把引用作为函数参数传递就叫引用
// 不可以修改借用的东西,引用也是默认不可变的,可以使用 mut来让引用可变  &mut 数据类型/参数
let len=calculate_length(&mut s1);//参数的变量也要是可以变的,否则会报错
    
println!("{},{}",s1,len);  
}

// 函数使用变量不获得所有权
fn calculate_length(s:&mut String) ->usize {//注意参数里面不是在变量前面加& ,而是在类型前面加&
    s.len()//不会取得所有权
}

fn first_world(s: &String ) -> usize {
let bytes=s.as_bytes();
for (i,&item) in bytes.iter().enumerate() {
   if item == b' '{
       return i;
   }
}
s.len()
}

??:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyvUSQ5g-1651467632361)(E:sysyPictures哔哩哔哩动画image-20220327105233388.png)]

??:通过引用传递参数给函数的方法就叫做借用!!!

???:可变引用

fn main(){
    
 let mut s=String::from("hello");
change(&mut s);  
    
}
// 可变引用
fn change(some_string: &mut String){
    some_string.push_str(", world");
    //相当拼接字符串的功能 append
}

:**限制点:在特定作用域中,对于某一块数据,只能有一个可变的引用(一次只能声明一个可变引用 )。**可以通过大括号来分隔作用域实现有多个可变引用

??:这里要多想想记住!!!!

// 可变引用有一个限制:要特定作用域内,对于某一块数据,只能有一个可变的引用 。
 let mut p=String::from("hello");
 let z1=&mut p;
 let z2=&mut p;//报错!!!违反了规则
 println!("{},{}",z1,z2);

上面的限制性规则可以帮助我们在编译时避免数据竞争。

??:数据竞争

以下三种行为会发生数据竞争:

  1. 两个或者多个指针同时访问同一个数据

  2. 至少有一个指针用于向空间中写入数据

  3. 没有使用任何机制来同步对数据的访问(没有同步访问)

// 可以通过创建新的作用域,来允许非同时的创建多个可变引用
//eg:
let mut k=String::from("hello");
{//可以大括号分隔作用域
    let s1=&mut k;
}//到这里s1就不再有效了,因为已经出了作用域了
let s2=&mut k;

??:可以通过花括号{ } ,来创建一个新的作用域范围,这样就可以创建多个可变引用 !!!

:限制:不可以同时拥有一个可变引用 和 一个不可变的引用;但同时有多个不可变的引用是可以的

let mut s=String::from("hello");
let r1=&s;
let r2=&s;
let s1=&mut s;//报错!!!!:因为不可以把s借用为可变的引用,因为它已经借给了不可变的引用 
println!("{},{},{}",r1,r2,s1);

悬垂引用

??**概念:**一个指针引用了内存中的某个地址,但是这块内存可能已经释放并且分配给其它变量使用了。

rust保证不会让引用进入悬垂状态!!!

??:这里我目前可以理解为:C++中的不要返回局部对象的引用,因为离开它自己的作用域也就被销毁了!!!

fn main(){
    // 悬空引用示例 
   let r=dangle();
}
fn dangle() -> &Stirng {//报错!!!
let s = String::from("hello");
&s  //s的引用返回给调用者,s在这里离开作用域并且被销毁,它指向的内存也就无效了
}
====================================================
//直接返回 String 就不会报错了
fn dangle() -> Stirng {
let s = String::from("hello");
s   //所有权被转移出函数并没有被销毁
}

??:引用的规则

  • 在任何一段给定的时间内,要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用
  • 引用总是有效的

切片(slicce)

切片(slicce):是 rust 中不持有所有权的数据类型。(允许我们引用集合中某一段连续的元素序列)

使用方式和go语言的切片差不多。

??:示例

fn main(){
    let mut s=String::from("hello");
let wordindex=first_world(&s);
println!("{}",wordindex);
// 字符串切片 和 
let s=String::from("hello world!");
let hello=&s[0..5];//左闭右开  中间数字之间也两个点
let hello=&s[..5];//等价于上面那个
let world=&s[6..11];
let world=&s[6..];//和上面那个一样
// 整个字符串
let u=[..];
}

方括号数字之间是两个点:

字符串切片的边界必须位于有效的 UTF-8 字符边界内。

[ start … end ] 是一个左闭右开区间

??:字符串切片的类型是: &str

因为 &str 是一个不可变的引用,所以字符串字面量自然也是不可变的

:字符串字面值实质上是一个切片

fn main(){
    let s1="hello";//字符串字面值实质是切片
 // 将字符串切片作为参数传递
// 使用 &str作为函数参数,这样就可以现时接String 类型和 & str 类型的参数,更加通用
// eg:
let my_string=String::from("hello world");
let wordindex=first_w(&my_string[..]);
let my_string_str="hello world";
let wordindex=first_w(&my_string_str[..]);//可以简化为下面这种形式,因字符串字面值本质是切片
let wordindex=first_w(my_string_str);
    
}


fn first_w(s: &String ) -> usize {
let bytes=s.as_bytes();
for (i,&item) in bytes.iter().enumerate() {
   if item == b' '{
       return i;
   }
}
s.len()
}



// 示例函数 参数为String引用建议改为这个
//因为这样改进后既可以处理 String类型又同时可以处理 &str 类型,更加通用
fn first_w(s:&str) -> &str{
let bytes=s.as_bytes();
for ( i,&item) in bytes.iter().enumerate(){
if item==b' '{
return &s[..i];
}
}
&s[..]
}

数组切片:

// 这个切片go语言中的切片用法差不多,go和rust中的切片数字都不能为负数
let a=[1,2,3,4,5];
let slice=&a[1..5];//数组切片;切片的第二个参数不可以像python那样写成负数

struct 结构体

//语法:
struct 结构体名  {
    字段名 : 类型 ,
    字段名 : 类型 ,
    ...          ,
    //最后一个字段也要有逗号
}


// 定义一个结构体在花括号里面为所有字段定义名称和类型
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool, //最后一个对象也要有逗号
}

?? 创建对象实例:不能只赋值其中几个字段,必须对所有字段赋值,顺序可以不一样

  //    创建一个对象实例
    // 不能只赋值其中几个字段,必须对所有字段赋值,顺序可以不一样
    let mut user1 = User {
        email: String::from("725482520"),
        username: String::from("shenyang"),
        active: true,
        sign_in_count: 1,
    };

使用 . 来说属性

    // 使用点 . 来访问属性
    println!("{}", user1.email);
    println!("{}", user1.username);
    println!("{}", user1.active);
    println!("{}", user1.sign_in_count);
   // 更改结构体的字段的值
user1.email = String::from("654321"); 
//前提要是创建对象是要可变的 加了 mut 关键字
//struct 的实例是可变的,那么实例中所有字段都是可变的

??**更新语法:**想用某个struct实例来创建一个新实例的时候可以使用更新语法

语法: … 对象实例名

   // struct更新语法:想用某个struct实例来创建一个新实例的时候可以使用更新语法
    let user2 = User {
        email: String::from("123456"),
        username: String::from("sy"),
        ..user1 
//在这使用了更新语法(也就是除了上面两个字段,其他的字段跟user1的一样)
    };

??:上面更新语法:user2除了自己定义的两个属性,其他的属性和 user1 相同。

struct可以作为函数的返回值

fn restr(e: String, u: String) -> User {
    User {
        email: e,
        username: u,
        active: true,
        sign_in_count: 1,
    }
}

字段初始化可以简写

//字段初始化可以简写,当字段名与字段值对应变量名相同时,就可以省略字段名
fn restr1(email: String, username: String) -> User {
    User {
        email, //可以使用简写方式
        username,
        active: true,
        sign_in_count: 1,
    }
}

struct 的实例是可变的,那么实例中所有字段都是可变的

结构体实例对象也分为可变和不可变的:

let mut 名称 = 结构体名 {
    对应字段赋值
}
====================================================
let  名称 = 结构体名 {
    对应字段赋值
}

Tuple struct

//语法:
struct 名称 ( 类型1 , 类型2 ,类型3 ... );
//最后一个类型不需要加逗号

struct Color(i32, i32, String, bool);

??:实例

// tuple struct 实例
let red = 
Color(255, 255,String::from("blacke"),true); 
//tuple struct 的实例
let mut black = 
Color(255, 255, String::from("blacke"), true); //tuple struct 的实例
black.0 = 246; //也可以使用点语法来访问元素
black.1 = 200; //想要改变必须创建时是可变的
black.2 = String::from("red");

使用点来访问属性,字段序号从 0 开始 !!!

struct 方法

方法第一个参数是self,相当于C++中的 this,方法可以有多个参数,但第一个必须是self***,在 impl 块里面定义方法**

每个 struct 允许拥有多个 impl 块

??:语法

impl  结构体名 {
    方法
}


// struct 方法
// 方法第一个参数是self,相当于C++中的 this,方法可以有多个参数,但第一个必须是self
//访问使用实例对象 加 . 访问
// 在 impl 块里面定义方法
// 每个struct允许拥有多个 impl 块
impl Rectangle {//绑定方法到 struct上   impl  结构体名 { 对应的方法 } 
    fn area(&self) -> u32 {//也有可变与不可变 self  &self  &mut self  对应 获得所有权  借用 可变借用
        self.width * self.length
    }

// 关联函数,是函数不是方法 通过用于构造器
//调用关联函数使用  类型名::函数名
    fn square(size:u32) -> Rectangle {//创建一个正方形
        Rectangle {
            width: size,
            length: size,
        }
    }
}

impl Rectangle {

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.length > other.length
    }

}

**关联函数:**不带 self 的函数

// 关联函数,是函数不是方法 通常用于构造器
//调用关联函数使用  类型名::函数名

 fn square(size:u32) -> Rectangle {//创建一个正方形
        Rectangle {
            width: size,
            length: size,
        }
    }

示例:

// 示例
// 计算长方形的面积
fn area(dim: (u32, u32)) -> u32 {
    dim.0 * dim.1
}

fn main(){
    
        let w = 30;
        let l = 50;
        let rect = Rectangle {
            width: w,
            length: l,
        };
        println!("{}", area(&rect));
        println!("{}", rect.area());//使用对象实例调用它自己的方法
        fn area(rect: &Rectangle) -> u32 {
            rect.width * rect.length
        }

        println!("{:?}", rect);

        println!("{:#?}", rect);
    
}

#[derive(Debug)] 
struct Rectangle {
    width: u32,
    length: u32,
}

impl Rectangle {//绑定方法到 struct上   impl  结构体名 { 对应的方法 } 
    fn area(&self) -> u32 {//也有可变与不可变 self  &self  &mut self  对应 获得所有权  借用 可变借用
        self.width * self.length
    }

// 关联函数,是函数不是方法 通过用于构造器
//调用关联函数使用  类型名::函数名
    fn square(size:u32) -> Rectangle {//创建一个正方形
        Rectangle {
            width: size,
            length: size,
        }
    }
}

枚举

使用 enum 关键字

//语法:
enum 名称 {
    字段1 ,
    字段2 ,
    ...
    //最后一个字段不需要加逗号
}


enum ipAddress{
    V4,
    V6
}

创建枚举对象

// 创建枚举
let four= ipAddress::V4;

let mut four= ipAddress::V4;
four=ipAddress::V6;

route(four);
route(ipAddress::V6);

fn route(ip : ipAddress){
    match ip {
        ipAddress::V4 => println!("ipv4"),
        ipAddress::V6 => println!("ipv6"),
    }
}

和 struct 组合:

enum ipAddress{
    V4,
    V6
}
// 和 struct 组合
struct add{
    ipkind:ipAddress,
    ip:String,
}

上面的可以由枚举变体替代:

//语法
enum 名称 {
    字段名 (类型1,类型2,类型3,...),
    字段名  (类型),
    ...,
    //最后一个字段也要加逗号
}


// 可以将数据附加到枚举的变体中,这样就可以不用像上面那样要使用struct,每个变体可以拥有
// 不同的类型以及关联的数据量
enum ipAdd{
    V4(u8,u8,u8,u8),
    V6(String),
}


// 枚举变体
let four=ipAdd::V4(127,0,0,1);
let six=ipAdd::V6(String::from("1k:8p:1o:9D"));

// 枚举和结构体可以相互嵌套也可以自己嵌套,枚举也可嵌套枚举

为枚举定义方法也可以使用 impl 关键字:

impl ipAdd {
    
fn show(&self){
match self {
        ipAdd::V4(a,b,c,d) => println!("{}.{}.{}.{}",a,b,c,d),
  ipAdd::V6(a) => println!("{}",a),
    }
}
}

示例:

enum Message{
    Quit,
    Move {x : i32 , y : i32},
    Write(String),
    ChangeColor(i32,i32,i32),
}
/*
Quit 没有任何关联数据
Move 包含了一个匿名结构体
Write 包含了一个String
ChangeColor 包含了3个 i32 值
*/

Option 枚举

定义在标准库中在 Prelude 中,用它来标识一个无值无效或缺失!

Option 是一个枚举,它可以有两个变体:SomeNone。(描述了某个值可能存放(某种类型)或不存在的情况)

rust 中没有 Null 或者 Nullable 的概念,而是使用 Option 来表示可能存在(有值)或不存在(无值)的情况。

标准库定义:

T 表示是一个泛型参数!!!

enum Option {//标准库定义
    Some(T),
    None,
}

示例:

// Option示例:
{
    let sn=Some(5);
    let ss=Some("a string");
    let absent_number: Option  = None;
}

注意:

  • Option 和 T 是不同的类型,不可以把 Option 直接当成 T
  • 如果想使用 Option 中的T,必须将它转换为 T,或者使用 match 语句来处理 None 值
let x:i8 =5;
let y:Option =Some(5);
//x 和 y 是两种不同的类型
let sum= x + y; //报错:必须把 y 转换为 i8 类型
println!("{:?}",sum);

match 和 if let

??:match 匹配必须穷举所有的可能性!!!

??:示例:

// 定义一个枚举
enum Coin{
  Penny,
  Nickel,
  Dime,
  Quarter,
}
//匹配 Coin 的所有可能性
fn value_in_cents(coin: Coin)  -> u8 {
 match coin{
    Coin::Penny =>1,
    Coin::Nickel =>5,
    Coin::Dime =>10,
    Coin::Quarter =>{
        println!("Quarter");
        25
        },
 }
}

如果处理的语句有多条,需要用大括号括起来!!!

匹配 Option 示例:记住它只有两种状态!!

// 匹配 Option
fn plus_one(x : Option ) -> Option {
    match x{
        None => None,
        Some(i) => Some(i+1),
    }
}
fn main(){
 let five = Some(5);
let six =plus_one(five);
let none = plus_one(None);  
}

??:如果情况太多,可以使用下划线通配符来代替其它情况,不用穷举所有的情况了!!!

// 如果值的情况有点多,不想列出所有的情况,可以使用 _ 通配符来替代没列出的值
// 示例:这样就可以不穷举所有可能了
let v=0u8;
match v{
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
    // 其他情况用 通配符替代
}

if let

??: if let 处理只关心一种匹配而忽略其它匹配的情况

let v =Some(0u8);
match v {//这里只处理 3 和其他 两种情况这样使用 if let 更好
    Some(3) => println!("three"),
    _ => println!("anything"),
}

上面只处理一种情况值为 3 情况,可以使用 if let 来处理

// 简洁写法
if let Some(3) = v{
    println!("three");
}

包、单元包、模块

??:

  • 包(package):一个用于构建、测试并分享单元包的Cargo 功能。
  • 单元包(crate):一个用于生成库或可执行文件的树形模块结构
  • 模块(module)及 usu 关键字:它们被用于控制文件结构、作用域及路径的私有性
  • 路径(path):一种用于命名条目的方法,这些条目包括结构体、函数和模块等

Cargo 会默认将 src/main.rs 视作一个二进制单元包的根节点而无须指定,这个二进制单元包与包拥有相同的名称

模块:以 mod 关键字来定义一个模块,接着指明这个模块的名字,用花括号包裹块体。

路径:

  • 使用单元包或字面量 crate 从根节点开始的绝对路径
  • 使用 self 、super 或内部标识符从当前模块开始的相对路径

标识符之间使用 :: 隔开。

//  定义模块
mod my_mod{
    pub  fn a(){
          println!("a");
      }
      fn b(){
          println!("B");
      }
      mod my_mod1{
          fn c(){
              println!("C");
          }
      }

    pub  mod my_mod2{
       pub fn c(){
            println!("C");
        }
    }
   
  }

fn main() {
crate::my_mod::my_mod1::c();//报错因为 my_mod1 是私有的
    
    crate::my_mod::my_mod2::c();
    my_mod::my_mod2::c();
    
}

如果模块没有 pub 属性修饰,就不能直接访问,但如果 pub 修饰了模块,没有修饰里面的函数可以访问模块,但不能访问里面的函数。

??:Rust 中的所有条目(函数、方法、结构体、枚举、模块及常量)默认都是私有的。

mod my_mod{
    pub  fn a(){
          println!("a");
      }
      fn b(){
          println!("B");
          my_mod2::c();
      }


      mod my_mod1{
          fn c(){
              println!("C"); 
            }
      }

    pub  mod my_mod2{
       pub fn c(){
            println!("C");
            crate::my_mod::a();
            super::a();
            crate::my_mod::b();
            //子模块可以使用它所有祖先模块中的条目
        }
    }
   
  }

??结论:在父模块中的条目无法使用子模块中的私有条目,但是子模块中的条目可以使用它所有祖先模块中的条目。

使用 pub 关键字来暴露路径

要注意一下使用 pub 关键字来暴露了模块,但是它里面的函数依然是私有的没有被暴露,要暴露某个函数必须要在前面加 pub 。

super 关键字是从父级模块开始构建相对路径,它可以相当于 linux 文件系统中的两个点 … 。

??:结构体定义时使用了 pub ,结构体本身成为了公共结构体,但它的字段依旧还是私有的,要一个一个字段的进行是否需要成为公共的。

 mod back_of_house{
    pub struct Breakfase{
        pub name:String,//公共的
            age:i16,//私有的
    }
    impl Breakfase{
        pub fn new (name:String) -> Breakfase{
            Breakfase{
                name:name,
                age:18,
            }
        }

    }

  }

fn main() {

    let s1=back_of_house::Breakfase::new(String::from("shenyang"));
}

??:我们将一个枚举声明为公共的时候,它所有的变体都自动变成为公共的,与结构体区分开

mod sy{
    pub enum xianze{
        A,
        B,
        C,
        D,
    }
impl xianze{
    pub fn show(&self){
        println!("A");
    }
}   
}
fn main() {
    let x1=sy::xianze::A;
    x1.show();
    let x2=sy::xianze::B;
}
用 use 关键字将路径导入作用域

使用 use 将路径引入作用域时也需要遵守私有性规则!!!

使用 use 指定相对路径必须要传递给 use 的路径的开始处使用关键字 self ,而不是从当前作用域中可用的名称开始。

使用 use 将函数的父模块引入作用域意味着在调用时我们必须指定这个父模块,从而更清晰的地表明当前函数没有定义在当前作用域中。

当使用 use 将结构体、枚举和其他条目引入作用域时,我们通常通过完整路径来引入而不是引入父级模块。

当引入的函数名称相同的时候,我们可以使用它们的父模块来区分两个不同的类型。

mod my_mod{
    pub  fn a(){
          println!("a");
      }
      fn b(){
          println!("B");
          my_mod2::c();
      }
   mod my_mod1{
          fn c(){
              println!("C"); 
            }
      }

    pub  mod my_mod2{
       pub fn c(){
            println!("C");
            crate::my_mod::a();
            super::a();
            crate::my_mod::b();
            //子模块可以使用它所有祖先模块中的条目
        }
    }
   
  }
use my_mod::my_mod1;//错误 my_mod1是私有的
  mod back_of_house{
    pub struct Breakfase{
        pub name:String,
            age:i16,
    }
    impl Breakfase{
        pub fn new (name:String) -> Breakfase{
            Breakfase{
                name:name,
                age:18,
            }
        }

    }

  }
//   use back_of_house::Breakfase;//绝对路径
//   use self::back_of_house::Breakfase;//相对路径

mod sy{
    pub enum xianze{
        A,
        B,
        C,
        D,
    }
impl xianze{
    pub fn show(&self){
        println!("A");
    }
}
    
}
use sy::xianze;
fn main() {
    let x1=sy::xianze::A;
    x1.show();
    let x2=sy::xianze::B;
    let s1=back_of_house::Breakfase::new(String::from("shenyang"));
    crate::my_mod::my_mod2::c();//报错因为 my_mod1 是私有的
    my_mod::my_mod2::c();
}
使用as 来指定引入的别名
use sy::xianze as xz;//使用 as 指定别名
fn main() {
    let x1=xz::A;
    
    let x1=sy::xianze::A;
    x1.show();
    let x2=sy::xianze::B;
使用 pub use 重导出名称
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}

使用 use 关键字将名称引入作用域的时候,这个名称会以私有的方式在新的作用域中生效,为了让外部代码访问这些名称,可以使用 pub use ,也被称为 重导出。

使用外部包

首先将它们列入 Cargo.toml 文件,再使用 use 来将特定条目引入作用域。

可以使用 嵌套的路径来清理众多的 use 语句:

use std::cmp::Ordering;
use std::io;
//==============================================
use std::{cmp::Ordering, io};

//使用 self
use std::io;
use std::io::Write;
//=================================================
use std::io::{self, Write};

//==============================================
use std::collections::*;//使用通配符

写出相同的部分,用花括号包裹有差异的部分。

可以使用通配符 * 来引入某个路径中所有的公共条目。

通用集合类型

use std::collections::HashMap;

//  vector 学习
fn main(){
// 只能存放相同类型的值
// 创建方式
let v:Vec=Vec::new();
//也分为可变和不可变


let mut v=vec![1,2,3];//vec后面有个 !

v.push(5);

// get 会返回一个 Option<&T>

match v.get(1){
    Some(x)=>println!("{}",x),
    None=>println!("None"),
}

// 读取 vector里面的值
println!("{}",v.get(0).unwrap());
// unwrap方式:标准库实现
// pub const fn unwrap(self) -> T {
//     match self {
//         Some(val) => val,
//         None => panic!("called `Option::unwrap()` on a `None` value"),
//     }
// }

match v.get(1){
    Some(i) => println!("{}",i),
    None => println!("None"),
}

println!("{}",v[0]);

// 索引 和 get 处理访问的越界的区别:索引: panic  get:返回 None

// 所有权和借用规则:不能在同一作用域内同时有可变的引用 和不可变 的引用
let mut v = vec![1,2,3,4,5];//可变
let first=&v[0];//不可变引用 
v.push(6);//可变借用
println!("{}",first);//不可变引用

// 遍历 vector的元素
for i in &v{
    println!("{}",i);
}
// 遍历同时更改值
let mut v = vec![100,120,130];
for i in &mut v{//要是可变引用
    *i+=50;//这里要解引用
}

// 示例存放多种类型
{
enum SpreadsheetCell{
Int(i32),
Float(f64),
Text(String),
}

let row=vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

}


// ==========================================================================
// String 类型
let mut v=String::from("hello");
let mut v1=String::from("hello");
v.push_str(",world");//append
v.push_str(&v1);
v1.push('!');//把单个字符添加到字符串的末尾
let v2=v+&v1;//使用+号来连接
// 使用 format! 拼接字符串
let s=String::from("t1");
let s1=String::from("t2");
let s2=String::from("t3");

let s=format!("{}-{}-{}",s,s1,s2);//它不会取得任何参数的所有权
println!("{}",s);


// rust 的字符串不支持索引访问
let he="hello";
let s=&he[0..4];


// hashmap
{
// key -values 存储方式
// use std::collections::HashMap; 引入才能使用
let hs:HashMap=HashMap::new();
// 在创建的时候没有数据,就要指定类型

let mut s=HashMap::new();
s.insert(String::from("hello"),10);//像这样 rust 就可以推导出它的类型了
// 另外一种方式创建 
let teams=vec![String::from("blue"),String::from("yellow")];
let initial_scores=vec![10,50];
let scores: HashMap<_,_>=
teams.iter().zip(initial_scores.iter()).collect();

for (k,v) in &scores{
    println!("{}: {}",k,v);
}
println!("{:?}",scores);
// hashmap 和所有权
/*
对于实现了 Copy trait 的类型, 值会被复制到 HashMap 中
对于拥有所有权的值,值会被移动,所有权会转移给 hashmap
如果将值的引用插入到 hashmap 中,值本身不会移动,在hashmap 有效期间,被
引用的值必须保持有效

*/

// 只丰 k 不对应任何值的情况下,才插入 entry() 返回值为枚举 
{
    let mut v = vec![1,2,3,4,5];
    let first=v[0];
    v.push(6);
    println!("{}",first);
}
}
}

?? 动态数组中:

&[ ] 会直接返回元素的引用

索引访问会因为访问不存在的元素而发生 panic,而 get 方法会返回 None 不会发生 panic。

注意所有权规则和借用规则:不能在同一个作用域中同时拥有可变引用和不可变引用。

当我们需要修改可变引用的值,需要先对其解引用 *

要在动态数组中存储不同的元素类型时,可以枚举来;因为枚举中的所有变体都被定义为了同一种类型。

pop 方法移除并返回末尾的元素


?? 字符串:

Rust中的字符串使用了 UTF-8 编码。

rust 内置的string 编码格式是 utf-8,如果使用其它编码格式,就会报错,除非自己实现一个解码器

rust核心部分只有一种字符串类型:字符串切片 str ,它通常会以借用形式出现: & str

可以对那些实现了 Display trait 的类型调用 to_string() 方法;

let data="shenyang";
let s=data.to_string();//把字符串字面量转换成String
let s1="sy".to_string();//也可以直接应用于字面量,s1的类型为String
let s2=String::from("sy");

String::from 和 to_string 完成 相同的工作。

??

更新字符串:

我们可以方便的使用 + 和 format! 宏来拼接字符串。(+ 方式会取得参数的所有权,而 format!不会取得参数的所有权)

let s=String::from("t1");
let s1=String::from("t2");
let s2=s+&s1;
//看下面方法知道:函数会取得 s 的所有权
==============================================
+ 号会调用一个方法
fn add(self , s: &str) -> String{...}
&s1能够调用add方法原因在于:编译器可以自动将 &String 类型的参数强制转换为  &str 类型。(解引用强制转换技术)

push_str 添加一段字符串切片;push 添加单个字符

字符串不支持索引访问。

遍历方法: chars() 、 bytes()

?? HashMap

要使用它要引入当前作用域:

use std::collections::HashMap;

它的键必须要有相同的类型,它的值也必须要 相同的类型。

要记住可变与不可变原则;

作用 zip 、 iter 、collect 配合使用可以将动态数组转换为哈希映射:

let teams=vec![String::from("blue"),String::from("yellow")];
let initial_scores=vec![10,50];
let scores: HashMap<_,_>=
teams.iter().zip(initial_scores.iter()).collect();

所有权:实现了 Copy trait 的类型,它们的值会被简单的复制到哈希映射中,对于持有所有权的值,值会被转移,并且所有权会转移给哈希映射 ;将引用插入进去就不会转移所有权,指向的值要保证在哈希有效时自己也要是有效的。

get 获取值,返回一个 Option。

entry 方法检测一个键是否存在对应值,如果不存在就为它插入一个值。

错误处理

不可恢复错误与 panic!

当 panic! 发生时,程序会默认从开始栈展开。可以在 Cargo.toml 文件中的 [profile] 区域添加 panic='abort’来改变 panic 默认行为从展开切换为 终止。

//显示调用 panic!
panic!("发生了错误!!!");

回溯信息:

将环境变量 RUST_BACKTRACE 设置为一个非0值,从而获得回溯信息。

RUST_BACKTRACE=1 cargo run

带有调试信息的回溯:

cargo build 或 cargo run 命令时,没有附带 - - release 标志,调试就是默认开启的

可恢复错误与 Result

enum Result {
Ok(T),
Err(E),
}
//Result 枚举定义了两个变体:Ok 和 Err


  use std::fs::File;
    let f=File::open("hello.txt");
  let f=  match f{
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}",error),
    };

匹配不同类型的错误:

{
    use std::fs::File;
    let f=File::open("hello.txt");
  let f=  match f{
        Ok(file) => file,
        Err(error) => match error.kind(){
            std::io::ErrorKind::NotFound => match File::create("hello.txt"){
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}",e),
            },
            other_error => panic!("Problem opening the file: {:?}",other_error),
        },

        };
    };

unwrap 和 expect (快捷方式)

unwrap :

当 Result 的返回值是 Ok 变体时,它会返回 Ok 内部的值。返回值是 Err 变体时,它会替我们调用 panic! 宏。

use std::fs::File;
fn main(){
    let f=File::open("hello.txt").unwrap();
}

expect:

它允许我 们在 unwrap 的基础上指定 panic! 所附带的错误提示信息。

fn main(){
    let f=File::open("hello.txt").expect("打开文件失败!!!");
}

传播错误

当执行失败的调用时,除了了可以函数中处理这个 错误,还可以将这个错误返回给调用者,这个过程就叫传播错误。

{
use std::io::{self,Read};
use std::fs::File;

fn read_username_from_file() -> Result {//将错误返回给调用者
    let f=File::open("hello.txt");

    let mut f=match f{
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s=String::new();

    match f.read_to_string(buf: &mut String){
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }//这里不加分号,表示发生错误返回错误,成功就忽略
}
}

传播错误的快捷方

相关文章