跳转至

rust程序设计语言完全入门

特别鸣谢: BiliBili 令狐一冲

rust前期学习基本都在看令狐哥的视频

https://space.bilibili.com/485433391

书籍参考: Rust 程序设计语言

https://kaisery.github.io/trpl-zh-cn/foreword.html

书籍参考: 张汉东 Rust 编程之道

https://weread.qq.com/web/reader/0303203071848774030b9d6

书籍参考: Rust 死灵书

https://www.bookstack.cn/books/rustonomicon_zh-CN

视频学习: 极客时间 张汉东的 Rust 实战课

https://time.geekbang.org/course/intro/100060601?tab=catalog

变量

什么是变量

程序在运行的时候就是运行的二进制数据, 是没有变量的信息

变量的信息只是我们程序员观察使用的

0000 0001  1234 1234 
1234 1234  1234 1234

变量的声明

变量使用 let 关键字 进行声明, 变量默认不可以改变

声明变量需要有一个类型, 这个类型可以我们手动标注, 或者编译器自动推导

let a = 123;  // 编译器自动推导
let b: u32 = 234;  // 手动标注
let c = "123";

let a = 123i8;
let a = 123_u8;

变量遮蔽

变量的重新声明赋值, 再次使用 let 时, 实际上创建了一块新"位置", 并为这一块"位置"绑定一个名字

let a = 123;
let a = 234;
println!("{a}");  // 234

变量遮蔽并不会导致原本变量的生命周期消失, 直到当前作用域结束, 自动释放

let a = 123;
{
  let a = 234;
  // 释放当前block中的a
}
println!("{a}");  // 123

可变变量

let 后添加 mut 关键字, 变量每次改变都会修改原本"位置"的数据

let mut a = 1;
a = 2;
println!("{a}");  // 2

注意 mut 关键字不可以修改数据的类型

let mut a = 1;
a = "2233";  // expected integer, found `&str`

常量

一般以大写声明(约定俗成), 类似于不可变变量, 但是常量不可以使用 mut, 声明常量使用 const, 且 必须注明值得类型, 常量可以在任何作用域声明包括全局作用域, 常量不能作为函数的返回

const MINUTE:u32 = 1423;
println!("{MINUTE}");  // 1423

变量的初始化

在rust中, 么有初始化的变量是禁止读取的

    // let x1: i32;
    // if true {
    //     x1 = 123;
    // };
    // // println!("x1: {}", x1);  // use of possibly-uninitialized `x1` 禁止使用, 因为编译器不知道判断条件是否成立 而对x1 进行初始化操作

    let x2: i32;
    loop {
        if true{
            x2 = 3344;
            break
        }
    }
    // println!("x1: {}", x2);  // 这里可以读取是因为, 编译器知道loop循环中只有一个break, 且和break同一个作用域中, 有对x2 进行赋值的操作, 所以这里编译器知道只要代码运行到这一行, 那是肯定已经对x2 进行赋值的

常用数据类型

bool类型

let is_true = true;
let is_false = false;

char类型

rust中 char类型 使用单引号 ' 包裹来声明一个单字符变量

char 占 4 个字节, 是 32 位的, 它是一个Unicode标量值, 所以可以使用中文和任意Unicode字符

let a: char = '你';
let a: char = 'h';
let a: char = 'ß';

数字类型

允许使用 _ 做为分隔符以方便读数, i8 i16 i64 u8 u16 u32 u64 f32 f64...

let a: i128 = -22_223;   // 有符号 32 位
let a: u32 = 22_333;     // 无符号 32 位
let a: f32 = 0.618;      // 浮点型 (rust 中浮点只有 f32 和 f64)
let a = 123i32;          // 有符号 32位

整形相除只能得到整形 的商, 整数运算必须使用相同类型(浮点数也一样)

let a: i32 = 56 / 32;
let a: f32 = 56 / 32;  // Error: expected `f32`, found integer

自适应类型

usize isize, 根据操作系统的不同有不同的值

println!("usize max: {}", usize::max_value());  // 18446744073709551615
println!("isize max: {}", isize::max_value());  // 9223372036854775807

unit类型

无返回值即unit类型,

fn say_hello() -> (){  // 无返回值 返回类型为 unit ()
    println!("hello");
};

赋值表达式的类型也为unit即 空tuple

let mut a = 0;
let x = (a = 2);
println!("{}", x);  // ()

复合类型: 元组

一旦声明, 其长度不会增大或缩小,元组中的每一个位置都有一个类型, 且这些不同值的类型也不必是相同的, 通过 tuple.index 访问

let tup: (i32, u32, char) = (-1314, 2233, 'a');  // 显式的指定类型
let mut tup = (-1314, 2233, 'a');                // 让 rust 自动推导
tup.0 = -223;                                    // 通过 . 下标访问元组元素 并进行修改

元素可以通过let模式匹配进行解构赋值

let tup: (i32, u32, char) = (-1314, 2233, 'a');
let (a, b, c) = tup;
println!("a: {a}, b: {b}, c: {c}");  // a: -1314, b: 2233, c: a

复合类型: 数组

一旦声明, 其长度不会增大或缩小, 数组中的每个元素的类型必须相同, 可以通过 array[index] 访问内部元素的引用

let arr: [char; 5] = ['a', 'b', 'c', 'd', 'e'];  // 显式指定类型
let arr = ['a', 'b', 'c', 'd', 'e'];  // 让 rust 自动推导
let arr = [0; 5]; // 创建一个长度为5每个元素都是0的数组

注意数组的size也是类型的一部分

fn show(_arr:[char; 3]) {  // 接收一个长度为 3 的数组
    for i in _arr.iter() {
        println!("{i}");
    }
}
let arr: [char; 5] = ['a', 'b', 'c', 'd', 'e'];
show(arr)  // expected an array with a fixed size of 3 elements, found one with 5 elements

无效的数组元素越界访问 会引发 panic

let arr = [0; 5];
arr[5]  // index out of bounds: the len is 5 but the index is 5

函数

函数的调用及定义

say_hello();
fn say_hello() -> (){
    println!("hello");
};

函数的参数

函数在定义时, 必须声明函数中接收参数的类型

fn say_info(name: &str) {
    println!("我叫 '{}'", name)
}
say_info("小明");

函数的返回值

在rust中, 如果有返回值,函数需要使用箭头指定函数的返回类型, 如果语句中没有分号结尾, 则最后一行表达式会被当做该函数的返回值

fn sum(a: i32, b: i32) -> i32{
    a + b
    // return a + b;  // 等价于上面的那句
}

函数出入参的解构赋值

函数中的出参入参等价于一个隐式的 let绑定,而 let绑定本身是一个模式匹配的行为

let tup = (1, 2, 3);
fn show(tup: (i32, i32, i32)) -> (i32, i32, i32) {
    return tup;
}
let (a, b, c) = show(tup);
println!("{a}, {b}, {c}");  // 1, 2, 3

包含语句和表达式的block

下面是一个代码块, 他的值是6, 注意这里 x + 1 是没有分号的, 表示这是一个表达式, 如果加上分号 ';' ,则变成了一个语句, 那么y就变成了一个unit ()

// 包含语句和表达式的块
let y = {
    let x = 5;
    x + 1
};
println!("{:?}", y);  // 6

控制流

分支判断

分支判断只能使用bool 值, 这和其他语言是不同的, 比如其他语言任何非 0 的值都为true, 在rust中则必须为 bool 值, 没有隐式类型转换

let a: u32 = 3;
if a > 5 {
    println!("a 小于 5, a 是: {}", a)
} else if a == 5 {
    println!("a 等于 5")
} else {
    println!("a 小于 5, a 是: {}", a)
}

值判断

println!("3 < 5: {}", 3 < 5);
println!("3 > 5: {}", 3 > 5);
println!("3 <= 5: {}", 3 <= 5);
println!("3 >= 5: {}", 3 >= 5);
println!("3 == 5: {}", 3 == 5);
println!("3 != 5: {}", 3 != 5);

三目运算

rust 里并没有提供类似语法, 但是赋值语句可以使用表达式, 注意! if else中的返回值必须是相同类型

let flag: bool = true;
let a = if flag {
    5
} else {
    10
};
println!("a: {a}");  // a: 5

loop

loop 无条件循环相当于 while true, 但是在 编译器 编译阶段 后续的处理不同, 在变量变量初始化提到过

let mut counter = 0;
loop {
    counter += 1;
    if counter == 100 {
        break
    }
    if counter % 10 == 0 {
        continue
    } else if counter % 2 == 0 {
        println!("in loop: {}", counter);
    }
};

while

循环内, 是返回单元值, 不能根据while block内的返回值进行类型标记, 因为while 并不一定执行

let while_type = || {
    while true {
        return 123;  // Error: expected integer, found `()`
    };
};

break/continue

循环体表达式返回 (通过 break 关键字, 使循环停止,并把后面的表达式执行结果返回)

let a = loop {
  break 1;
};
println!("{a}"); // 1

for

for循环其实 是IntoIterator trait的糖, 会需要循环类型的所有权

let arr: [char; 3] = ['a', 'b', 'c'];
// for i in &arr{
for i in arr.iter(){
    println!("in for: {}", i)
}
let v = vec![1];
// for i in &v{  // 这个可以运行是因为 循环的是 &v, 而&v 是拥有Copy语义的
for i in v{  // 注意这是无法运行的, 因为下面println了v

}
println!("{v:?}")

模仿goto

break语句和continue语句可以在多重循环中选择跳出到那一层循环, 在 loop while for 循环前加上生命周期标识符, 即可实现阉割版 "goto" 功能

let mut c = 0;
'a: for i in 0..10 {
    'b: for d in 0..10{
        c += 1;
        println!("{}", d);
        if c < 5 {
            continue 'a;
        } else {
            break 'b;
        };
    }
}

所有权

rust中赋值表达式默认是一个自动的copy操作(可以先这么理解), 如果没有实现Copy trait, 那么会自动执行move,

栈中的所有数据都必须占用已知且固定的大小

Rust 中的每一个值都有一个被称为其 所有者(owner)的变量, 值在任一时刻有且只有一个所有者

当所有者(变量)离开作用域,这个值将被丢弃 执行 drop 相关

变量的作用域

let a: i32 = 1;
{
    let b: i32 = 2;
    println!("a in scope: {}", a);
    println!("b in scope: {}", b)
}
// println!("{}", b)  // error: not found in this scope

String类型和堆

String有三部分组成: (它本身其实是一个vector)

  • 1.一个指向堆中存放具体内容内存的指针,
  • 2.长度: 当前使用了多少字节的内存,
  • 3.容量: 从操作系统总共获取了多少字节的内存
// String 结构体源码
pub struct String {
     vec: Vec<u8>,
}

String具体的内容分配在堆上的, 而字面量是因为字面量在编译时就知道了内容,提前分配了内存所以直接硬编码进了可执行文件, 对于String类型,为了支持一个可变的文本片段,需要在运行时向操作系统请求内存,并将String这个管理这段内存的变量分配到栈上

let mut s = String::from("hello");  // 当调用 String::from 时, 请求起所需内存, 当String变量离开作用域的时候会调用 drop 方法清理内存
s.push_str(" world");  // 动态添加数据
println!("String pushed: {}", s);
// let mut s = "abc";  // 创建一个字面量 &str类型 变量
// s.  // 由字面量创建的变量, 很多修改方法都是没有的

move语义

对保存堆中具体数据的指针的栈数据进行复制的时候, 注意 String类型被移动的时候,只是从栈上拷贝了原本的指针 长度 和容量, 但是有一个问题两个变量的中保存的指针都指向了同一个位置,由于String类型在离开作用域的时候回自动执行 drop 方法清理内存,则这两个变量都会尝试释放相同的内存,这就引发了 double free 的问题, 所以 rust 在进行这种移动操作类似浅拷贝完成的时候,会同时使第一个变量失效,只剩下最新的变量有效

let x = String::from("hi");
let y = x;
// println!("x: {}", x);  // error: borrow of moved value: `x`
println!("y: {}", y);

clone语义

使用clone 使存放 保存数据的堆 的指针 的栈中的数据 进行复制后, 依然有效,其原理是堆中的数据复制一份到新内存中并让新变量保存的指针指向该内存地址

clone语义需要使用者主动使用

let x = String::from("say");
let y = x.clone();
println!("Clone String x: {}", x);

copy语义

对存放栈中的数据是一般来说直接拷贝(需要看Copy trait具体实现), 不会使变量失效, 原因是像整型这样的简单数据类型在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的, 没有理由在创建变量 y 后使 x 无效

Copy trait 对于使用者, 是自动使用的

let x = 2;
let y = x;  // 实现了Copy trait 这里自动调用copy方法
println!("x: {}", x);
println!("y: {}", y);

Copy trait

如果一个类型拥有 Copy trait,一个旧的变量在将其赋值给其他变量后旧的变量仍然可用

Copy trait`继承自`Clone trait`,意味着,要实现`Copy trait`的类型,必须实现`Clone trait

rust为一些类型实现了Copy trait: 所有整数类型比如 u32, 布尔类型,bool 它的值是 true 和 false, 所有浮点数类型比如 f64, 字符类型char, 元组,当且仅当其包含的类型也都是 Copy 的时候;比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是

所有权与函数

将值传递给函数在语义上与给变量赋值相似(发生隐式 let 绑定), 向函数传值也会发生移动,或者复制, 就像赋值语句 let x = y 一样

fn say_name(name:String){
    println!("hi i am {}", name)  // 这里之后作用域结束形参离开作用域,并调用 drop 方法
}


fn say_age(age:u32){
    println!("hi, i age is {}", age)
}


let x = String::from("abc");  // x 变量进入作用域
let y = 123;  // y 变量进入作用域

say_name(x);  // x 的值移动到函数中, 之后函数的形参接收该值, 但是!由于形参接收该值之后,在函数执行完毕之后,内部的作用域会执行drop方法,包括形参, 所以下面已经不再有效
println!("x: {}", x);  // error: borrow of moved value: `x`

say_age(y);  // y 是 u32 是 Copy 的,所以后面可以继续用 y
println!("y: {}", y);

所有权与返回值

fn f1()->String{
    let x = String::from("abc");  // x 进入作用域
    x  // 返回 x 并移出给调用的函数
}
fn f2(s:String)->String{
    s  // 返回 s 并移出给调用的函数
}
let s1 = f1();  // f1 将返回值移给 s1
let s2 = String::from("ohh");  // s2进入作用域
let s3 = f2(s2);  // s2 被移动到 f2 中, 但 f2 也将它移动到了 s3中, 所以 "ohh" 现在的所有权在 s3 中, 不在s2 中

所有权与解引用操作

如果原变量没有实现 Copy trait, 解引用操作会获得该变量的所有权, 从而发生move(可能会造成 "野指针")

let s = String::from("hello");
let s1 = &s;
println!("s: {}", s);
// let s2 = *s1;  // 如果这里解引用,获得了 s 的所有权, 发生move, s2获得了s的所有权, 由于s1是 s的引用, s1 就变成了 "野指针"

不可变引用本身与copy

不可变引用会实现Copy trait, 因为不可变的引用本来就可以存在多个

let a = "hello".to_string();
let b = &a;
let x = b;  // 这里发生copy 注意这里 x和b 是同一个生命周期
let f = || b;  // 这里发生copy
drop(b);
f();  // 可见上面drop掉, 是不会影响闭包内的变量的( ps: 这里是没有drop的, 因为不可变引用是实现了 copy trait 的, 而实现了copy trait的变量是无法drop的, 只能通过 scope销毁)
let s = "hello".to_string();
let s1 = &s;
let s2 = s1;  // s1 copy 到 s2
s1;  // 因为实现了copy 上面没有发生所有权转移, 这里依旧可以使用s1
s2;
s1;
s2;
println!("s1: {}, s2: {}", s1, s2);

Drop语义

在x持有某个对象的所有权的情况下,对x赋值,那么x原来持有的对象就会drop, 因为这个"位置" 已经被占用了, 所以需要对旧值drop

  • 就是当x持有某个对象所有权, 如果 x 绑定到了另外一个变量, 那么原来的变量就发生了移动会被加上 drop-flag 标签,在运行时会调用析构函数, 加上 drop-flag的变量意味着生命周期的结束

如果作为函数的返回值, 没有对应的接收者, 那么返回值会立即析构 let _ = function()

#[derive(Debug)]
struct Student{name:String};
impl Drop for Student{
    fn drop(&mut self){
        println!("清理了: {}", self.name)
    }
}


let mut x = Student { name: "x1".to_string() };
let mut y = x;  // 这里 x 已经变为一个空的变量了, x所有权转移到了y

println!("1");
x = Student { name: "x2".to_string() };  // 对空变量重新赋值
x = Student { name: "x3".to_string() };  // 再次赋值挤走了 x2, 此时x2执行析构函数
x = Student { name: "x4".to_string() };  // 再次赋值挤走了 x3, 此时x3执行析构函数
println!("2");
y = x;  // 对 y 重新赋值, y本身保存的是x1, 这时x4 把 x1 挤走, x1被drop了

println!("3");
x = Student { name: "x5".to_string() };  // 上面 x本身的变量的所有权被移走了, 这里对空变量x 重新赋值
println!("4");
y = x;  // x里现在是x5, 挤走了y中的x4, y中 x4被析构 替换为了x5, x为空,

println!("5");
x = Student { name: "x6".to_string() };
println!("6");
y = x;
println!("7");

// TODO 神奇 接着上方, 下面有一个疑点, 为啥打印: x2 被那么快析构
// 1
// 清理了: x2
// 2
// 清理了: x3
// 3
// 清理了: x1
let mut x = &mut Student { name: "x1".to_string() };  // let 将临时变量的生存期延长到当前作用域结束
println!("1");
x = &mut Student { name: "x2".to_string() };  // temp的生存期只到该行语句末尾
println!("2");
x = &mut Student { name: "x3".to_string() };
println!("3");

// 接着上方, 为啥第二行解开注释 就报错, 提示使用了一个临时变量被立即释放了(因为不是let语句, 生命周期没有延长, 2233的string直接释放了)
let mut a = &mut "123".to_string();
// a = &mut "2233".to_string();
println!("{}", a);

Drop的一些总结

https://doc.rust-lang.org/stable/reference/destructors.html?highlight=temp#temporary-lifetime-extension

let语句中通过引用或可变引用表达式的临时范围有时扩展到包含let语句的块的范围

let mut x = &S { name: "tmp1" }; let 语句, 这句会触发rust语言里的临时生存期延长功能, 产生一个直到当前块作用域结束的生存期, 记录在x 的类型里: x: &'<special lifetime> S 这个是let里才有的

然后第二句 x = &S{ name: "tmp2"}; 这句没啥特别的, 如果不是nll, 这句应该报错, 但是反正你也没用, S 构建出来了, 然后又析构了

let绑定一个temp的引用, 会将temp的生存期拓展到作用域结束(一般temp的生存期只到语句末尾)

强行将temp的引用赋值给x, x就用不了了, 使用则禁止编译, 因为temp在赋值语句结束就drop了 这也就是上面的这个例子解开第二行注释就出错的原因

let mut x = &mut 0; // let语句中表达式的临时范围有时会 扩展到包含该let语句的块的范围, 如果借位, 解除引用, 字段或元组索引表达式具有扩展的临时范围, 其操作的临时变量 也将扩展包含该let语句的块的范围
println!("x 可以打印: {}", x);
x = &mut 1;  // 临时变量立即释放, 且x本来位置也被覆盖
// println!("x 禁止打印: {}", x);  禁止使用 Error: creates a temporary which is freed while still in use
let mut s = &String::from("123");
s = &String::from("11");
println!("{:?}", s);

// 会出现如下错误:
// 93 |     s = &String::from("11");
//    |          ^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
//    |          |
//    |          creates a temporary value which is freed while still in use
// 94 |     println!("{:?}", s);
//    |                      - borrow later used here
//    |
// help: consider using a `let` binding to create a longer lived value

复杂类型的Drop顺序

如果复合结构本身实现了Drop,则会 先调用它自己的析构函数 后调用其成员的析构函数;否则,如果本身没有析构函数会调用其成员的析构函数

Rust中变量的析构顺序是和其声明顺序相反的, 元组内部是按元素的出现顺序依次进行析构的, 结构体内部元素的析构顺序是按排列顺序来析构的

struct Stu1{name:String};
impl Drop for Stu1{
    fn drop(&mut self){
        println!("清理了: Stu1")
    }
}
struct Stu_meta{s:Stu1}
impl Drop for Stu_meta{
    fn drop(&mut self){
        println!("清理了: Stu_meta")
    }
}
let s_meta = Stu_meta {s: Stu1{name:"Stu1".to_string()}};
{
    s_meta;  // 这里语句会  let _ = s_meta;
}
println!("s_meta END");

安全的Drop

一个安全地实现 Drop 的类型, 它的泛型参数生命周期必须严格地长于它本身(Rust死灵书, Drop检查一节, 只有泛型需要考虑这个问题)

struct Inspector<'a>(&'a u8);

impl<'a> Drop for Inspector<'a> {
    fn drop(&mut self) {
        println!("再过{}天我就退休了!", self.0);
    }
}
// let (inspector, days);  // 同时声明变量, 不可知谁被先销毁 如果days碰巧先被销毁了, 那么当销毁Inspector的时候, 它会读取被释放的内存(目前好像已经更新版本了?, 编译器会先drop 前面的? 有待测试)
// days = Box::new(1);
// inspector = Inspector(&days);

实现Copy trait类型的销毁

实现可 Copy 的类型 只能通过 scope 进行销毁, 满足Copy特质的, 位置里面的值无法被移走, 换句话说, 不存在存储位置存在但是里面的值已经死亡的情况

同样, 实现了 Copy的类型, 它是无法实现 Drop的

let i = 123;
drop(i);  // 无法drop
{
    i;  // 对于 实现了 Copy trait的类型, 子作用域显然无法 销毁上级作用域的变量
}
i;  // 依然使用

// 实现了 析构行为的 类型, 无法 实现 Copy
// #[derive(Copy, Clone)]  // 无法实现 Copy trait 会报错: `Copy` not allowed on types with destructors
struct Students{
    age:i32
}
impl Drop for Students{
    fn drop(&mut self){
        println!("被释放了: {}",&self.age)
    }
}

引用/借用

栈中的所有数据都必须占用已知且固定的大小, 引用的大小是已知并且固定的, 你可以将数据(比如堆中的数据)的引用存储在栈上, 不过当需要实际数据时, 必须通过引用访问

借用`是一个`动作`一般常用于`右值`, `借用`后产生一个`引用

引用必须总是有效的

共享不可变,可变不共享: 在不可变借用生命周期期间,所有者不能修改资源, 并且也不能再进行可变借用; 在可变借用期间,所有者不能访问源资源,并且也不能再出借所有权

!! 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用, 在不可变引用失效之前, 不能创建可变引用

基本使用

fn get_len(s:&String)->usize{
    s.len()
}
fn push_str(s:& mut String){
    s.push_str(" world");
}

let mut s1 = String::from("hello");  // 创建一个变量

let s = &s1;  // 通过借用创建一个引用
println!("&s1: {}", s);

let ms = &mut s1;  // 创建一个可变引用(之前的引用都无法使用, 失效,禁止使用, 不能在拥有不可变引用的同时拥有可变引用)
println!("&mut s1: {}", ms);
//  println!("&s1: {}", s);  error 无法使用之前的引用,因为在可变引用期间无法访问不可变借用

push_str(ms);  // 通过函数直接操作指针指向的数据

let l = get_len(&s1);
println!("修改后 '{}' 的长度为: {}", s1, l);

不可变引用和可变引用交叉

不能在拥有不可变引用的同时拥有可变引用, 可以同时存在, 但不能同时存活(生命周期发生交叉)

let mut s = String::from("hello");
let r1 = &s; // 没问题
let r3 = &mut s; // 没问题 同时存在, 后面不再使用
let r2 = &s; // 没问题
let r4 = &mut s; // 没问题 同时存在,
// println!("{}, {}, and {}", r1, r2, r3);  // 上面会error mutable borrow occurs here 不允许使用, 可变引用和不可变引用同时存活, 生命周期发生了交叉, 如果使用则上方会出错

一个引用的作用域/生命周期

引用的作用域/生命周期; 是从声明的地方开始 -> 一直到最后一次使用结束

let mut s = String::from("say");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用, 下方可以借用可变引用
// 因为最后一次使用不可变引用在声明可变引用之前, 不可变引用 r1 和 r2 的作用域在 println! 最后一次使用之后结束

let r3 = &mut s; // 没问题, 再次出借
println!("{}", r3);

垂悬引用

在rust中, 编译器会执行严格的检查防止垂悬引用

let reference_to_nothing = dangle();
fn dangle() -> &String {  // dangle 返回一个字符串的引用
    let s = String::from("hello");  // 创建一个字符串
    &s  // 返回字符串 s 的引用
}  // 这里 s 离开作用域并被丢弃;其内存被释放, 所以上面函数返回的是一个空的引用

引用的所有权返回

可变引用(借用)的变量,会发生所有权转移,在这个可变引用(最后一次使用) 生命周期结束 后,所有权还是会回到原来的变量上去; 因为只能有一个可变引用, 所以这里rust 使用的所有权转移保证只存在一个可变引用

上面的解释感觉有点问题, 可能这里只是单纯的 可变借用期间,所有者不能访问源资源

let mut s1 = String::from("hi");
let s2 = &mut s1;
println!("s2: {}", s2);  // 后面没有 s2 的使用, 此时s2的生命周期结束, 被销毁掉了
println!("s1: {}", s1);  // 没问题, s2 销毁掉之后, 这里 println 使用的 s1 的不可变引用
// 下面是错误代码以及说明
// println!("s1: {}", s1);  // 这里 println 默认使用了 s1 的不可变引用, 但是因为 后续仍然有 s2 的使用, 所以 s2 这个可变引用 并没有被 销毁, 违反了 可变引用和不可变引用 同时 存活 的规则
// println!("s2: {}", s2);

冻结

数据被借用时,它还会冻结(freeze); 已冻结的数据无法通过原始 对象来修改或者转移所有权,直到对这些数据的所有引用生命周期结束为止,保证引用数据的准确

let mut b = 1;
{
    let c = &b;
    // b = 2;  // error: assignment to borrowed `b` occurs here, 因为 b 的引用, 在下面还在使用,所以引用还没结束,这里无法修改分配
    println!("c: {}",c);
}

解引用操作和copy

解引用操作 可能会 因为后续的操作而可能被消耗, 获得所有权(可能会造成 "野指针", 所以对于未实现COPY trait的类型解引用可能会发生move),如果一个没有实现Copy trait的对象 通过引用 解引用, 那因为没有实现Copy,所以会发生move, 如果发生了 move, 那么原本的引用就是"野指针"(这在rust中是禁止的)

let s = String::from("hello");
let s1 = &s;
println!("s: {}", s);
// let s2 = *s1;  // 如果这里解引用, s2获得了s的所有权, s1是 &s, s1 就变成了 "野指针"
struct F1;
impl F1{
    fn func(self){}  // 这里使用的self, 在调用时会获取 self 所有权
}
struct F2{
    fs:Vec<F1>
};
impl Drop for F2{
    fn drop(&mut self){
        for f in &mut self.fs{
            // f.func();  // 这里自动解引用相当于 调用了 (*f).func(), 因为上方存在可变引用f, 而func方法使用的是self会 转移/出借 所有权, 所以这里无法解引用的(在可变借用期间,所有者不能访问源资源,并且也不能再出借所有权, 这里解引用也会让其内元素尝试copy然后尝试move)
        }
    }
}

reborrow

不可变引用类型的变量是实现了Copy trait 的, 当对一个引用变量赋值给其他变量, 那会Copy 一份到新的变量上 他们共用一个生命周期

可变引用变量进行赋值操作, 会发生所有权转移, 可变引用类型是没有Copy在赋值操作会move引用变量, (写出变量注解,即变成reborrow)

可变引用类型是move 的, 写上注解会让 borrow 变为reborrow

reborrow相关操作,有问题会存在两个可变引用,并不是真的存在两个可变引用, 而是中间发生了所有权转移, 和所有权回归

let mut s = "hello".to_string();
let s1 = &mut s;
// let s2 = s1;  // 这里 s1 的所有权给了 s2, 下面再使用s1则无法编译通过, 注意 这和 let s2 = &mut s;是不一样的, 这里虽然s2类型一样, 但是一个是转移其所有权, 一个是重新借用
let s2:&mut String = s1;  // s1 所有权 move 到 s2, 可变引用赋值操作需要使用变量类型注解变成 reborrow
s2.push_str("123");
println!("{s2}");  // s2 生命周期到此结束, 所有权返回归本身的s1, 生命周期没有发生交叉(这里同时可以存在两个可变引用就是因为这个原因)
println!("{s1}");

let mut s = "hello".to_string();
let s1 = &mut s;
// let s2 = &mut s;  // 有问题! 下面还在使用s1, 这里发生了borrow mut, 违反借用规则(同时存在两个可变借用,生命周期发生交叉)

let s2 = &mut *s1;  // 没问题! 这里 *s1 是一个临时变量 忽略借用规则, 这句可以看成这样 &mut(*s1) 其中 *s1是一个temp
// let s2:&mut String = s1;  // 没问题! s1它是一个可变引用 move 到 s2, 使用变量注解

s2;  // let _ = s2; s2 生命周期到此结束, 被drop 所有权返回s1, 两段可变生命周期并没有发生交叉, 如果这两句打印调换一下位置, 则会编译错误,因为违反借用规则
s1;  // s1 生命周期到此, 无问题

slices

切片与引用

let s1 = String::from("hello,world");
println!("s1: {}", s1);
let s1_bytes = s1.as_bytes();
for (idx, data) in s1_bytes.iter().enumerate() {
    println!("idx: {}, data: {}", idx, data)
}
for i in s1_bytes.iter(){
    println!("{}", i)
};

使用切片来获得字符串的部分引用

let s1 = String::from("hello,world");
println!("&s1[0..5]: {}", &s1[0..5]);  // 看左不看右
println!("&s1[1..=5]: {}", &s1[1..=5]);  // 看左同时看右
println!("&s1[..=5]: {}", &s1[..=5]);  // 从头开始切片
println!("&s1[1..]: {}", &s1[1..]);  // 切片到尾部
println!("&s1[..]: {}", &s1[..]);  // 默认全部

注意 中文字符的步长为 3 目前这里处理 utf8 字符会有一些问题

let s2 = String::from("你好");
// println!("&s2[..]: {}", &s2[0..2]);  error
println!("&s2[..]: {}", &s2[0..3]);
println!("&s2[..]: {}", &s2[..]);

切片与字面值

字面值其实是slice

let s3 = "我很好";

其他类型的slice

let arr1:[i32; 4] = [1,2,3,4];
let slice_arr1 = &arr1[0..2];
println!("&arr1[..]: {}  {}, len: {}", slice_arr1[0], slice_arr1[1], slice_arr1.len());

结构体

结构体是不会自动实现Copy Trait的, 需要我们手动实现Copy, 并且我们实现Copy 的时候 必须在之前实现 Clone

声明与创建

// 定义一个结构体
struct Student {
    name: String,
    age:u32,
}

基本使用

// 定义一个结构体
struct Student {
    name: String,
    age:u32,
}

// 创建一个结构体实例
let stu1 = Student{
    name:String::from("小明"),
    age:12
};

// 修改结构体字段 需要添加 mut 关键字
let mut stu2 = Student {
    name: String::from("小花"),
    age:18
};
stu2.age = 17;
println!("学生2 的年龄为 {}", stu2.age);

快速创建结构体

参数名字和字段名字同名的快速创建对象的方法

struct Student {
    name: String,
    age:u32,
}


fn create_stu(name:String, age:u32) -> Student {
    Student{
        name,
        age
    }
}
let stu3 = create_stu(String::from("小亮"),28);
println!("学生3 的名字为 {}", stu3.name);

从其他的结构体创建实例(使用 .. 指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值)

let stu4 = Student {
    ..stu3  // 注意,因为所有权的原因, stu3.name 被解构出来了,在离开作用域时已经被删除了
};
// println!("学生3 的名字为 {}", stu3.name);  // error 和上面呼应, 在创建 stu4 的时候, s3部分字段被析构了
println!("学生4 的信息除了名字, 其他都是来自 学生3 的, 名字为 {}, 年龄为: {}", stu4.name, stu4.age);

元组结构体

字段没有名字 通过下标访问, 每一个一个结构体有其自己的类型,即使结构体中的字段有着相同的类型

struct Point(i32, i32);
struct Color(i32, i32);
let a = Point(10, 20);
println!("a.0: {}, a.1: {}", a.0, a.1);

单元结构体

单元结构体实例就是其本身 struct S; let s1 = S; let s2 = S; 在Debug模式下 s1s2是不同的内存地址, 在release编译模式下,他们进行了优化是相同的内存地址

无字段的结构体

没有任何字段的结构体(类单元结构体), struct Dog{};

结构体的输出打印

#[derive(Debug)]
struct Teacher {
    name:String,
    age:u32
}
let t1 = Teacher{
    name:String::from("张三"),
    age:29
};
println!("t1的所有信息: {:?}", t1);
println!("t1的所有信息: {:#?}", t1);  // 自动换行打印

结构体的方法

#[derive(Debug)]
struct People {  // 创建一个结构体
    name:String,
};
impl People {  // 实现结构体的方法
    fn get_name(&self)->&String{  // 传入实例的引用
        &self.name  // 这里返回 String 的引用, 防止 String 被清理
    }
    fn eat(&self){
        println!("我在吃饭");
    }
};

let p1 = People{
    name:String::from("李四")
};

p1.eat();
println!("我叫: {}", p1.get_name());
println!("p1 所有的信息: {:#?}", p1);

关联函数

允许在 impl 块中定义 不 以 self作为参数的函数, 因为没有 self 所以这里它们仍是函数 而不是方法,,因为它们并不作用于一个结构体的实例

struct User{
    name:String
}
impl User {
    fn new(name:String) ->User {
        User{
            name
        }
    }
}
let user1 = User::new(String::from("小亮"));
println!("通过关联函数使用结构体名和 :: 语法来调用这个关联函数, 用户1的名字为: {}", user1.name);

结构体内的所有权

注意 rust 中的类型的方法, 方法内部不允许内部转移类型本身元素发生 move,因为本身的元素发生了move, 那么这个类型就变成"残缺" 的了, 本身可以使用 clone

struct S{
    next:String,
    current:String
}
impl S{
    fn update(&mut self, value:String){
        // self.current = self.next;  // 这里是禁止的
        self.current = self.next.clone();
        self.next = value;
    }
}
ps: 上面可以使用self.current = mem::replace(&mut self.next, value);快速交换两个变量, mem::replace() 函数会返回被替换的值, 因此不需要调用 clone() 来复制 self.next

枚举类型

声明与创建

#[derive(Debug)]
enum Message {
    Quit,  // 没有关联任何数据 相当于 struct Quit;
    Move{x: i32, y:i32},  // 包含一个匿名结构体 相当于 struct Move {x:i32, y:i32}
    Write(String),  // 包含单独一个String 相当于 struct Write(String)
    Change(i32, i32, i32)  // 包含三个i32 相当于 struct Change(i32, i32, i32)
};

// 为 enum 实现一些方法
impl Message {
    fn call(&self){
        match &self {
            Message::Quit => println!("this is Quit"),
            Message::Move{x,y} => println!("this is Move"),
            Message::Write(s) => println!("this is Write"),
            Message::Change(a, b, c) => println!("this is Change a: {}, b: {}, c: {}, &self: {:?}", a, b, c, &self),
            _ => println!("上面没有被匹配的走到了这里: {:?}", &self)
        }
    }
};

let cm = Message::Change(1,2,3);  // 创建了一个拥有类型 Change(1, 2, 3) 的变量 m, 这就是当 m.call() 运行时 call 方法中的 self 的值
cm.call();
let wm = Message::Write(String::from("h"));
wm.call();

Option

Option 枚举类型(是标准库定义的一个枚举)

enum Option<T>{
    Some(T),
    None
}
定义 Option类型
let some_number = Some(5);
let some_string = Some(String::from("hi"));
let none:Option<i32> = None;
println!("some_number: {:?}", some_number);
println!("some_string: {:?}", some_string);
println!("none: {:?}", none);
// Option 类型的使用(使用 match 对 Option 类型匹配的时候, 需要把内部的类型全部匹配上)
let mut temp: i32 = 0;
match y {
    Some(i) => {println!("this is Some: {}", i); temp = i;}
    None => println!("this is None")
};
println!("x + y(temp) = {}", x + temp);

枚举类型与函数指针

枚举本质类似函数指针 如下代码推导 可见 function 和 枚举 在签名上没差, IpAddr2::V4 类似函数 fn(String) -> IpAddr2 的指针

#[derive(Debug)]
enum IpAddr2{
    V4(String)
};

let i1 = IpAddr2::V4(String::from("127.0.0.1"));
let i2 = IpAddr2::V4(String::from("0.0.0.0"));


let i4 = |s:String| -> IpAddr2{ IpAddr2::V4(s)};
let i3:fn(String) -> IpAddr2 = IpAddr2::V4;  // 竟然是一个函数签名

match匹配

注意使用match时, 需要给枚举的情况都包括, 如果懒得匹配可以使用 _ 来进行通用匹配

// 使用 match匹配 Some中值为3的情况
let some_u8_value = Some(3);
match some_u8_value {
    Some(3) => println!("match, three"),
    Some(2) => println!("match, two"),
    Some(1) => println!("match, one"),
    _ => (),
}

if let 与枚举

可以不使用 match 匹配, 来处理只匹配一个模式的值而忽略其他模式的情况, 使用 = 分隔的一个模式和一个表达式

// if let 可以快速进行指定值匹配
let y = Some(10);
if let Some(v) = y {
    println!("if let {}", v)
} else {
    println!("None")
}
// 使用if let 快速匹配 Some中值为3的情况
if let Some(3) = some_u8_value{
    println!("if let, three")
}

Vector类型

线性序列:向量, 也是一种数组,和基本数据类型中的数组的区别在于,向量可动态增长

创建和读取

// 创建空的 vector Vec<T>
let mut v:Vec<u32> = Vec::new();

// 创建包含初始值的 vector vec![...]
let v = vec![1,2,3];

vector读取元素 (同 array 读取一样, 可以使用get方法传入index,返回Option<&T>, 是rust推荐的方法,因为索引越界不会错误,会被None捕捉)

安全的修改元素我们可以通过 get_mut 来获得指定下标的可变引用

let v = vec!['a', 'b', 'c'];
let v_1:&char = &v[1];
let v_2:char = v[1];
println!("v_1: {}", v_1);
println!("v_2: {}", v_2);

let v_3:Option<&char> = v.get(2);
println!("v_3: {:?}", v_3);

match v.get(1) {
    Some('b') => println!("通过 match 匹配 b"),
    Some('c') => println!("通过 match 匹配 c"),
    None => println!("没有找到"),
    _ =>()
}
match v.get(999) {
    Some('b') => println!("通过 match 匹配 b"),
    Some('c') => println!("通过 match 匹配 c"),
    None => println!("没有找到"),
    _ =>()
}

更新元素

let mut v2:Vec<i32> = Vec::new();
v2.push(1);
v2.push(2);
v2.push(3);
println!("经过了3次push: {:?}", v2);

使用枚举元素

enum Context{
    Text(String),
    Float(f64),
    Int(i32)
};

let v1 = vec![
    Context::Text(String::from("hi")),
    Context::Float(0.618),
    Context::Int(64),
];

不可变遍历

let mut v2:Vec<i32> = vec![1, 2, 3, 4];

for i in &v2 {
    println!("不可变遍历 值为: {}", i)
}

可变遍历

let mut v2:Vec<i32> = vec![1, 2, 3, 4];

for i in & mut v2{  // 不推荐, 最好使用 iter_mut
    *i += 1
}
println!("经过了可变的遍历: {:?}", v2);

更新和 借用/引用 冲突

在拥有 vector 中项的引用的同时向其增加一个元素, 之前的不可变引用是无法再使用了,这是因为:在 vector 的结尾增加新元素时, 在没有足够空间 将所有所有元素依次相邻存放的情况下, 可能会要求分配新内存并将老的元素拷贝到新的空间中,这时,第一个元素的引用就指向了被释放的内存, 借用规则阻止程序陷入这种状况

let mut v3 = vec![1, 2, 3, 4, 5];
let first = &v3[0];  // 1. 读取了一个不可变引用
v3.push(9);  // 2. vector 修改了下标为0的值 这里其实使用了 v3的可变引用, 在使用了可变引用之后, 之前的所有不可变引用后面都无法使用
// println!("first: {}", first)  // 3. 这里 无法 使用了 first 的不可变引用

Vector 标准库练习

as_slice

返回 Vector的 slice

let v = vec![3, 2, 1, 4, 7];
let v = v.as_slice();
let v = &v[..];
let v: &[_] = v;

borrow/borrow_mut

获得 Vector 的不可变/可变 引用

let mut v = vec![3, 2, 1, 4, 7];
let v_borrow:&[i32] = v.borrow();
let v_borrow_mut:&mut [i32] = v.borrow_mut();
let v_borrow_mut = v.as_mut_slice();  // 这个签名好像和上面的没差别 哈哈都是 &mut [i32]

contains

检测 Vector 中是否有指定的值, 返回 bool, 其实内部还是用的 iter.any

let v = vec![3, 2, 1, 4, 7];
let ret = v.contains(&3);

capacity

返回 Vector的容量: 无需重新分配即可容纳的容量

with_capacity(cap) 方法会函数则会分配指定的 cap 大小的内存块, 此时它的长度还是 0, 容器的容量和容器的长度是两个不同的概念

通过这种方式分配的内存块是没有初始化的, 它指向的内存块中可能还保存有上一个用户的痕迹比如字节

let v:Vec<i32> = Vec::with_capacity(4);
let size:usize = v.capacity();

chunks

返回特定大小 切块的迭代器, 对 Vector 进行 切块

let v = vec![3, 2, 1, 4, 7];
let mut chunk_iter = v.chunks(2);
let chunk1:Option<&[i32]> = chunk_iter.next();  // Some([3, 2])
let chunk2:Option<&[i32]> = chunk_iter.next();  // Some([1, 4])
let chunk3:Option<&[i32]> = chunk_iter.next();  // Some([7])
println!("{:?}", chunk3);

clear

清除所有元素, 内部调用的 truncate 方法截断为0个元素

let mut v = vec![3, 2, 1, 4, 7];
let ret = v.clear();

String

创建的方式

创建一个空的字符串

let mut s1:String = String::new();
s1.push_str("S1");
println!("s1: {}", s1);

使用 String::from(), 通过字面值创建一个 String

let s2_1: String = String::from("S2_1");
println!("s2_1: {}", s2_1);

使用 str 的方式

let s2:String = "S2".to_string();
println!("s2: {}", s2);

可以预分配内存, 避免频繁的进行内存分配, 分配的大小至少能够再 插入 n 个元素

let mut s = "123".to_string();
s.reserve(10);

println!("{:?}",s.len());  // 3
println!("{:?}",s.capacity());  // 13

更新String

pust_str(需要传入 &str, 这里传入push_str的参数是字符串的 slice,所有权并没有转移)

let mut s3:String = String::new();
let s = "push_str".to_string();
s3.push_str(&s);
s3.push_str("push_str");

push只能传入 char 字符

let mut s3:String = String::new();
s3.push('p');
s3.push('u');
s3.push('s');
s3.push('h');
println!("经过了 一些列的更新后的值为: {}", s3);

使用 "+" 合并字符串(注意 使用了 + 之后 所有权会转移, 就相当于获取s1 的所有权,附加上从 s2 中拷贝的内容,并返回结果的所有权, 类似 fn add(self, s: &str) -> String { ,这里self没有使用&self 所以所有权会被转移)

let s3_1 = String::from("hello");
let s3_2 = String::from("world");
let s3_3 = s3_1 + &s3_2;  // 这里 s3_1 的所有权已经被转移了
println!("使用了 + 进行字符串合并: {}", s3_3);
// println!("s3_1: {}", s3_1);  error: value borrowed here after move

索引和slice

str 索引(由于rust中使用utf8, 一个中文占3个字节,或者其他数量字节, 所以rust不推荐使用索引来访问字符串元素,因为 Rust 不得不检查从字符串的开头到索引位置的内容来确定这里有多少有效的字符;, 可以使用slice, 注意slice 越界问题,)

let s = String::from("你好");

println!("通过字符串slice访问字符串: {}", &s[..3]);  // 你
// println!("通过字符串slice访问字符串: {}", &s[..4]);  // error: thread 'main' panicked at 'byte index 4 is not a char boundary; it is inside '好' (bytes 3..6) of `你好`'

遍历

chars (得到字符串的字符)

for char in s.chars() {
    println!("s.chars: {}", char)
}

bytes / as_bytes返回每一个原始字节, 字符串切片的字节上的迭代器, as_bytes性能更快, 没有进行复制之类的操作

for byte in s.bytes() {
    println!("s.bytes: {}", byte)
}
for byte in s.as_bytes() {
    println!("s.as_bytes: {}", byte)
}

格式化字符串

format! 格式化字符串( 由于是一个宏且使用的引用, 所以所有权不会转移)

let s1 = String::from("hello");
let s2 = "world";
let s3 = format!("{}, {}", s1, s2);
println!("format!: {}", s3)

HashMap

HashMap是无序的,BTreeMap是有序的

Key必须是可哈希的类型,Value必须是在编译期已知大小的类型

创建的方式

新建一个HashMap, 数据存放在堆中

let mut h: HashMap<&str, i32> = HashMap::new();
h.insert("小亮", 18);
h.insert("小花", 20);
println!("新建了一个HashMap: {:?}", h);

使用vector 的 collect 方法, 对于当时无法确定的类型可以使用 _ 占位, 让rust推导

let names = vec!["小强", "小李"];
let ages = vec![22, 17];
println!("names.iter().zip(ages.iter()): {:?}", names.iter().zip(ages.iter()));
let h2: HashMap<_, _> = names.iter().zip(ages.iter()).collect();
println!("h2: {:?}", h2);

HashMap和所有权

键值对被插入后就为 Hashmap 所拥有

let n:String = String::from("张三");
let mut h: HashMap<String, i32> = HashMap::new();
h.insert(n, 19);
// println!("n: {}", n)  // error: value borrowed here after move

访问HashMap中的值

使用get(key)获取的是Option, 或者使用遍历的方式无顺序的获取

println!("通过get获取-> 张三的年龄为: {:?}", h.get(&String::from("张三")));
for (k,v) in h.iter() {
    println!("通过循环遍历获取-> {} : {}", k, v)
}

更新HashMap

覆盖一个值

let mut h = HashMap::new();

h.insert("李四", 20);
println!("覆盖前: {:?}", h);  // 20
h.insert("李四", 22);
println!("覆盖后: {:?}", h);  // 22

只在键没有对应值时插入, 其他是更新

使用 entry or_insert, Entryor_insert 方法在键对应的值存在时就返回这个值的Entry可变引用( &mut V ), 如果不存在则将参数作为新值插入并返回修改过的 Entry可变引用( &mut V )

let mut h = HashMap::new();

h.insert("小花", 19);
let r = h.entry("李四").or_insert(25);
println!("Entry 的 or_insert方法返回的值: {}", r);  // 25
println!("使用entry on_insert 插入: {:?}", h);  //  {"李四": 25, "小花": 19}

根据旧值更新一个值

or_insert 方法事实上会返回这个键的值 的一个可变引用( &mut V );这里我们将这个可变引用储存在 count 变量中,所以为了赋值必 须首先使用星号( * )解引用 count

// 统计每个字符出现的次数
let s = "hello, world";
let mut h: HashMap<char, i32> = HashMap::new();

for i in s.chars() {
    let count  = h.entry(i).or_insert(0);
    *count += 1
}
println!("统计结果: {:?}", h)

模块

基本使用

定义并使用一个公开的模块(模块内部默认私有的), 使用pub关键字可以声明一个公开的模块

mod handle1 {
    // 定义一个子模块
    pub mod people {
        pub fn say(){
            println!("人 打招呼")
        }
    }
    pub mod dog {
        pub fn eat() {
            println!("狗 吃骨头")
        }
    }
}

handle1::dog::eat();

使用外部库的结构体

对于模块中的结构体中的字段, 如果外部公开访问则需要使用pub声明

pub mod students {
    #[derive(Debug)]
    pub struct Student {  // 定义一个结构体
        pub name:String,
        age:u32
    }
    impl Student {

        pub fn new(name:String, age:u32) -> Student{
            Student{
                name,
                age
            }
        }
        pub fn get_info(&self){
            println!("{:?}",self)
        }
    }
}


use mylib::utils::students::Student;
let stu1 = Student::new(String::from("小明"),18);
stu1.get_info();
stu1.name;  // 公开的属性外部可以直接获取
// stu1.age;  // 无法获取没有公开的属性 error: field `age` of struct `mylib::utils::students::Student` is private

调用外部网路上的库

现在不推荐这种方法了, 直接在Cargo.toml中声明更好

extern crate crypto;
use crypto::sha3::Sha3;

let mut hasher = Sha3::sha3_256();
hasher.input_str("hello, world");
let ret = hasher.result_str();
println!("{:?}", ret)

异常

rust 中错误分为两个类别, 可恢复错误/不可恢复错误

  • 可恢复错误: 通常表示错误和重试操作是合理的情况, 例如没找到文件, rust中使用Result<T, E> 来实现
  • 不可恢复错误: 通常是bug引起的, rust中通过panic!这个宏实现

最好使用 ? 处理错误, 传递到上方调用者 而不是使用 unwrap引发panic

使用 panic! 手动引发异常

panic!("出错了");

Result

T代表成功时返回的Ok成员中的数据类型, E代表失败Err成员中的数据类型, Result通过组合有效输出的数据类型和错误的数据类型来使用类型;例如,如果有效输出的数据类型为u64且错误类型为String ,则返回类型应为Result

// enum Result<T, E>{  
//     Ok(T),
//     Err(E)
// }

map error

改变 Err, 接收一个闭包 如果变量为err, 则err的值使用闭包调用的结果, 就是将 Result<T, E> 变为 Result<T, F>

let f = |x: i32|->String{ format!("出错了: {}", x)};
let o:Result<i32,i32> = Ok(123);
let o_ret: Result<i32, String> = o.map_err(f);
println!("o_ret: {:?}", o_ret);

let e:Result<i32,i32> = Err(2233);
let e_ret: Result<i32, String> = e.map_err(f);
println!("e_ret: {:?}", e_ret);

and

对两个Result 进行组合, 如果x是ok 那么就返回y, 如果x不ok 那么就返回x本身的Err, 参考 与操作 短路运算

let x: Result<i32, i32> = Ok(0);
let y: Result<i32, i32> = Err(1);
println!("x.and(y): {:?}", x.and(y));  // Err(1)  这里x是ok的,这里返回了y的值

let x: Result<i32, i32> = Err(0);
let y: Result<i32, i32> = Ok(1);
println!("x.and(y): {:?}", x.and(y));  // Err(0)  // 这里x是Err的 返回x本身的Err

let x: Result<i32, i32> = Err(0);
let y: Result<i32, i32> = Err(1);
println!("x.and(y): {:?}", x.and(y));  // Err(0)  // 这里x是Err的 返回x本身的Err

let x: Result<i32, i32> = Ok(0);
let y: Result<i32, i32> = Ok(1);
println!("x.and(y): {:?}", x.and(y));  // Ok(1)  // 这里x是ok 的, 这里返回了y的值

and then

基于结果值的回调, x.and_then(|x| {...}), 如果x是ok的, 那么x结果进入一个闭包可以再次处理, 如果不ok 那么返回x的Err

unwrap_or

提供了一个默认值default, 当值Err时返回default

let x: Result<i32, _> = Err(2233);
println!("x.unwrap_or: {}", x.unwrap_or(7788));

kind

返回简单的错误信息

fn test_fn() -> Result<(),Error>{
    use std::fs;
    let f = fs::read("./xxxxx.txt");
    match f {
        Ok(_) => {}
        Err(e) => println!("出现错误: {:?}", e.kind())  // 出现错误: NotFound
    };
    Ok(())
}
println!("{:?}", test_fn());

unwrap

快速获得结果, 如果Option类型具有Some值或Result类型具有Ok值,则其中的值将传递到下一步, 如果是Err则为我们调用panic!(慎用)

let f = File::open("../1.txt").unwrap();
println!("简写方式-1, {:?}",f);
// Result 简写方式-2 自定义异常信息, 这也会引发 panic
let f = File::open("../1.txt").expect("出错了");
println!("简写方式-2, {:?}",f);

异常传播

fn open_file() -> Result<String, io::Error>{  // 如果没有任何错误 函数的调用者会收到一个包含 String 的 Ok 值 ,如果函数遇到任何错误函数的调用者会收到一个 Err 值,它储存了一个包含更多这个问题相关信息的 io::Error 实例
    let f = File::open("../1.txt");
    let mut f = match f {
        Ok(file) => file,  // 读取成功返回给 f
        Err(e) => {
            return Err(e);  // 如果出错直接结束函数运行
        }
    };
    println!("处理中");
    let mut s = String::from("");

    let r =  match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),  // f读取成功之后写入通过引用写入到了 s 中, 所以这里 s 内容是已经写好的了, 直接返回 Ok(s) 到外部即可
        Err(e) => Err(e)  // 结果返回 r
    };
    return r
};

let r = open_file();  // 获得函数中的结果, 是 Ok(s), 或是 Err(s)类型

match r {
    Ok(s) => println!("读取成功: {}",s),
    Err(e) => println!("出错了: {:?}", e)
};

传播错误简写(使用?简写,如果遇到错误 Err 中的值将作为整个函数的返回值, 注意 ? 只能被用于返回值类型为 Result 的函数)

fn open_file() -> Result<String,io::Error>{
    let mut f = File::open("../1.txt")?;

    let mut s = String::new();

    // let ret = match f.read_to_string(&mut s) {
    //     Ok(_) => Ok(s),  // 这里返回 Ok(s)即可, 这里的 s 之前已经通过引用传入 f.read_to_string 内了,如果是Ok 那么这里 s 中的内容, 应该是读取的内容
    //     Err(e) => Err(e)
    // };
    // return ret  // 返回 Ok(s) 或者 Err(e)

    // 上面的注释简写为
    // f.read_to_string(&mut s)?;  // 这一行代码如果出错, 通过 ? 简写异常捕捉则直接返回 Err(e)
    // Ok(s)

    // 连写的方式
        let mut s = String::new();
        File::open("../1.txt")?.read_to_string(&mut s)?; // 读取文件 1.txt 如果没读取到直接返回 Err(e) 如果读取到往下走执行 .read_to_string 如果没有运行成功则直接返回Err(e), 如果执行成功因为没带";"号 则最后一句直接返回Ok(s)
        Ok(s)
}

match open_file() {
    Ok(s) => println!("读取成功, 内容为: {}", s),
    Err(e) => println!("读取失败, 错误为: {}", e)
}

Result 信息获取

使用ok 或者 err 来获取一个 option 类型的成功或者失败信息

let o: Result<i32, &str> = Ok(8);
let e: Result<i32, &str> = Err("message");
println!("o.ok: {:?}", o.ok(),);    // o.ok: Some(8)
println!("o.err: {:?}", o.err());   // o.err: None

println!("e.err: {:?}", e.err());   // e.err: Some("message")
println!("e.ok: {:?}", e.ok());     // e.ok: None

分层的错误处理

在Rust中 Panic只能被任务的所有者捕获, 而捕获后需要立即对他进行处理, 否则任务自己会立即停止

catch_unwind 捕捉恐慌(代码传入一个闭包中运行), 可以再不启动一个线程的情况下捕获Panic

use std::panic;
let a = 1;
let b = 0;
let ret = panic::catch_unwind(|| a / b);
match ret {
    Ok(x)=>(),
    Err(e)=> println!("catch_unwind捕捉恐慌: {:?}", e)
};

泛型

解决的问题

比如 有一个比大小的函数 需要对 [i32][char] 类型的array比较大小, 当有几种类型就需要定义几个function

fn max1(li: & [i32]) -> i32{
    let mut m = li[0];
    for i in li.iter() {
        if *i > m {
            m = *i
        }
    };
    return m;
}
let mut arr1: [i32;5] = [1, 2, 3, 4, 5];
println!("arr1最大的值为: {}", max1(&arr1));  // arr1最大的值为: 5

fn max2(li:&[char]) -> char {
    let mut m = li[0];
    for i in li.iter(){
        if *i > m {
            m = *i
        }
    }
    m
}
let arr2 = ['a', 'b', 'c'];
println!("arr2最大的值为: {}", max2(&arr2));  // arr2最大的值为: c

定义泛型函数

<> 尖括号中补充 对 泛型的 特征trait 约束; PartialOrd: 表示可以该泛型实现了数据比较, Copy: 表示T类型具有COPY特征

fn max3<T:PartialOrd + Copy> (li:&Vec<T>) -> T {
    let mut m = li[0];
    for i in li.iter(){
        if *i > m {
            m = *i
        }
    }
    return m;
}
let arr3_1 = vec![1, 2, 3, 4, 5];
let arr3_2 = vec!['a', 'b', 'c', 'd'];
println!("通过泛型函数, arr3_1最大的值为: {}", max3(&arr3_1));
println!("通过泛型函数, arr3_2最大的值为: {}", max3(&arr3_2));
fn max<T: PartialOrd>(li: &Vec<T>) -> &T {
    let mut m = &li[0];
    for i in li.iter() {
        if i > m {
            m = i;
        }
    }
    return m;
}

let mut v = Vec::new();
v.push(1);
v.push(0);
v.push(3);
v.push(2);
println!("max: {}", max(&v))

定义泛型结构体

#[derive(Debug)]
struct Point<T>{
    x:T,
    y:T
};

let p1 = Point{
    x:1,
    y:2
};
println!("通过泛型结构体, 定义的为: {:?}", p1);

let p2 = Point{
    x:2.2,
    y:3.4
};
println!("通过泛型结构体, 定义的为: {:#?}", p2);

使用多个泛型, 使泛型结构体使用不同类型

#[derive(Debug)]
struct Point2<T, U>{
    x:T,
    y:U,
};

let p3 = Point2{
    x:1,
    y:"2"
};
println!("通过泛型结构体使用不同类型: {:?}",p3);

枚举中的泛型

枚举可以和结构体一样使用泛型

常见的标准库中的 OptionResult就是这样

enum Option<T>{
    Some(T),
    None
};
enum Result<T, E>{
    Ok(T),
    Err(E)
}

方法中的泛型

impl 之后声明泛型, 这样 Rust 就知道Student<T, U>尖括号中的是泛型而不是具体的类型

#[derive(Debug)]
struct Student<T, U>{
    name:T,
    age:U
}
impl<T,U> Student<T,U>{  // 这里的 T和U 是结构体里面的类型的表示
    fn get_name(&self) -> &T {
        &self.name
    }
}

let stu1 = Student {
    name: "小明",
    age:18
};

泛型特化

对某些类型进行特殊处理, 一个没有在 impl之后(的尖括号)声明泛型的例子,这里使用了一个具体类型, 这里就意味着 Point<f64> 的实例 会有一个具体的方法 get_x, 而其他的类型的实例就没有这个方法

struct Point_<T>{
    x:T,
    y:T
}
impl Point_<f64>{
    fn get_x(&self) -> f64 {
        self.x
    }
}
let p1 = Point_ { x: 1, y: 2 };
let p2 = Point_ { x: 0.5, y: 3.1 };
// p1.get_x();  // error: method not found in `stu15::Point_<{integer}>`
p2.get_x();  // 只有 f64类型的实例才会有方法
struct Point<T, U>{
    x:T,
    y:U,
};

// 根据约束 特化, 这里两个泛型都必须实现 Copy trait 才会有这里的方法
impl<T:Copy, U: Copy> Point<T, U> {
    fn show(&self){

    }
}

// 根据类型 特化, 这里第二个泛型 必须是f32类型的才有这个方法
impl<T:Copy> Point<T, f32> {
    fn show(&self){

    }
}

let p = Point{x:1, y:String::from("123")};
p.show()  // 没有这个方法

泛型混合使用

使用学生1的name 和学生2 的age 组成一个新的stu3

let stu1 = Student { name: "小亮", age: 12 };
let stu2 = Student { name: "小明", age: 14 };

impl<T,U> Student<T,U>{
    fn create(&self, other:Student<T, U>) -> Student<&T, U>{  // 这里T和U可以继续使用, 是因为 这里 other 和self是同样的类型且下面调用字段也是同样的类型 
    // fn create<Q, W>(&self, other:Student<Q, W>) -> Student<&T, W>{  // create<Q, W, Y, K, L> 声明使用了泛型类型 Q, W, Y, K, L
        &self.name;
        Student {
            name: &self.name,
            age:other.age
        }
    }
}
let stu1 = Student { name: "小亮", age: 12 };
let stu2 = Student { name: "小明", age: 14 };
let stu3 = stu1.create(stu2);
println!("泛型混合使用创建了stu3的信息: {:#?}",stu3)

Trait

trait 以抽象的方式定义共享的行为, 用于定义与其他类型共享的功能

“所有的trait都定义了一个隐式的类型Self, 它指当前实现此接口的类型, ” ——Rust官方文档

self用作函数的第一个参数时, 它等价于self: Self, &self参数等价于self: &Self, &mut self参数等价于self: &mut Self

trait 基本使用

// 定义 trait
pub trait GetInfo {
    fn get_name(&self) -> &String;  // 函数接收一个参数 &self, 返回 &String 类型
    fn get_age(&self) -> i32;
}

// 定义结构体
#[derive(Debug)]
pub struct Student{
    name:String,
    age:i32
}

pub struct Teacher{
    name:String,
    age:i32,
    subkey:Vec<String>
}

实现Trait: 使用 impl trait_name for struct_name, 对trait中定义的接口函数 在结构体中对函数一一实现

impl GetInfo for Student {
    fn get_name(&self) -> &String {
        &self.name
    }
    fn get_age(&self) -> i32 {
        self.age
    }
};
impl GetInfo for Teacher {
    fn get_name(&self) -> &String {
        &self.name
    }
    fn get_age(&self) -> i32 {
        self.age
    }
};


let stu1 = Student{name:"小亮".to_string(), age:18};
let t1 = Teacher{name:"张三".to_string(), age:28, subkey:vec!["历史".to_string(), "语文".to_string()]};
println!("学生1的名字是: {}, 年龄: {}", stu1.get_name(), stu1.get_age());
println!("老师1的名字是: {}, 年龄: {}, 老师教的课有: {:?}", t1.get_name(), t1.get_age(), t1.subkey);

trait 默认实现

trait中实现函数的代码, 作为函数的默认行为,当然也可以进行重写

// 定义trait
trait SchoolName {
    fn get_school_name(&self) -> String {
        "默认-红星小学".to_string()
    }
}
// 这里把上面trait, 绑定到结构体中, trait中已经默认实现了函数的行为, 所以可以不用再实现相关方法
impl SchoolName for Student{};
impl SchoolName for Teacher{
    fn get_school_name(&self) -> String{
        "老师覆盖, 红星小学".to_string()
    }
}

let stu1 = Student{name:"小亮".to_string(), age:18};
let t1 = Teacher{name:"张三".to_string(), age:28, subkey:vec!["历史".to_string(), "语文".to_string()]};

println!("学生学校是: {}",stu1.get_school_name());  // 学生学校是: 默认-红星小学
println!("老师学校是: {}",t1.get_school_name());  // 老师学校是: 老师覆盖, 红星小学

trait bound 参数的约束

trait bound 特征约束语法糖,定义泛型,泛型中使用 <T:trait1 + trait2> 对函数的泛型进行特征约束, 如果使用多个特征进行约束 可以使用 + 来连接, 编译器会确保其被限制为那些实现了特定 trait 的类型

// 定义 trait
pub trait GetName{
    fn get_names(&self) -> &String;
}
pub trait GetAge{
    fn get_ages(&self) -> i32;
}
impl GetName for Student{
    fn get_names(&self) -> &String{
        &self.name
    }
}
impl GetAge for Student{
    fn get_ages(&self) -> i32 {
        self.age
    }
}

// 写法1: print_info_1 泛型T, 泛型T 必须实现 GetInfo 这个方法
fn print_info_1<T:GetName + GetAge>(item:&T){
    println!("trait bound语法糖实现特征, 写法1 name = {}, age = {}  ", item.get_names(), item.get_ages());
};

// 写法2: 使用 where T: 特征1 + 特征2 来对对函数进行约束
fn print_info_2<T>(item:&T)
where T: GetName + GetAge
{
    println!("trait bound语法糖实现特征, 写法2 name = {}, age = {}  ", item.get_names(), item.get_ages());
}

// 调用
let stu1 = Student{name:"小亮".to_string(), age:18};
print_info_1(&stu1);  // trait bound语法糖实现特征, 写法1 name = 小亮, age = 18  
print_info_2(&stu1);  // trait bound语法糖实现特征, 写法2 name = 小亮, age = 18  

返回值的约束与impl静态分发

特征约束函数的返回, 使其返回值必须有相关特征

fn get_item() -> impl GetName + GetAge {
    Student{
        name:"小花".to_string(),
        age:16
    }
}
let s = get_item();
println!("get_item限制了函数的返回: name: {}, age:{:?}", s.get_names(), s.get_ages());

注意: impl 限制函数返回使用特征约束的时候, 无法使用不同的结构体,因为虽然都实现了特征中的方法, 但是不同结构体却是不同类型, 而impl属于静态分发,编译期已经展开代码了

这种方式隐含地要求所有传入的闭包或函数都具有相同的类型, 尽管在实践中, 只要闭包或函数签名兼容, 这通常不是问题, 但理论上它限制了灵活性

fn get_() -> impl GetName{
    if true {
        return Student {
        name: "小李".to_string(),
        age: 12
        }
    }

    return Teacher {  // error: expected struct `stu16::Student`, found struct `stu16::Teacher`
        name: "张三".to_string(),
        age: 12,
        subkey:vec!["数学".to_string()]
    }
}

结构体字段的trait约束

使用 trait bound 对结构体的方法进行特征约束 impl 结构体, 只有实现了相应约束的实例, 才会有这个包含在大括号的方法

也可以看做是泛型特化

// 定义结构体, 这个结构体本身有学生和老师
struct People<T, U> {
    master:T,
    student:U
}

// 定义结构体的方法, 要求 T和U 需要满足对应的特征, 才会有对用的方法
impl <T:GetInfo, U:GetInfo> People <T, U>{
// impl <T, U> People <T, U>{
    fn print_all_name(&self){
        println!("master name: {}",self.master.get_name());
        println!("student name: {}",self.student.get_name())
    }
    fn print_all_age(&self){
        println!("master age: {}",self.master.get_age());
        println!("student age: {}",self.student.get_age())
    }
};

let t = Teacher { name: "李老师".to_string(), age: 23, subkey: vec!["政治".to_string()] };
let s = Student { name: "小环".to_string(), age: 12 };

let p1 = People{
    master:t,
    student:s
};
p1.print_all_age();
p1.print_all_name();

再看下面的例子, 找出不同

trait GetName{
    fn get_name(&self) -> String;
}

trait GetAge{
    fn get_age(&self) -> i32;
}

impl GetName for String {
    fn get_name(&self) -> String {
        self.clone()
    }
}


// 要求在创建结构体时, 就要检查Trait约束, 否则创建结构体失败
struct People<T: GetName>{
    field:T
}

let p = People {
    field: "".to_string()
};
p.field.get_name();


// 和上面不同的是, 这没有约束结构体创建的条件, 传进来什么类型都可以创建成功
// struct People<T>{
//     field:T
// }
//
// 只有调用的时候才会发现, 也就是说只有字段满足了 GetName 约束 才会有下面的方法
// impl<T:GetName> People<T> {
//     fn get_name(&self) {
//         self.field.get_name();
//     }
// }
// impl GetName for String {
//     fn get_name(&self) -> String {
//         self.clone()
//     }
// }
// let p = People { field: "123".to_string() };
// p.get_name();

在结构体定义中的约束

// 这个约束定义了 People 结构体实例化的规则, 
// 意味着每当使用 People 结构体时, 作为类型参数 T 的任何类型都必须实现了 Copy trait
// 这影响了结构体存储的数据以及结构体实例的创建, 它限制了可以用来实例化 People 的类型范围
struct People<T:Copy>{

}

在实现块中的约束

// 这个约束是在实现 People 结构体的方法时对泛型参数 T 加上的,
// 它限定了这个实现块内的方法(即使这里没有具体方法列出)只能被那些实现了 Copy trait 的类型 T 调用
// 如果 People 结构体有多个实现块, 每个块可以有不同的泛型约束, 这样就可以为不同特性的类型 T 提供不同的行为实现

impl<T:Copy> People<T>{

}

trait 的 trait约束

在满足某个trait之前, 必须满足前置trait, 这样我们就可以在 trait中实现 某些方法

trait GetN{
    fn get_name(&self) -> &String;
}
trait PrintN{
    fn print_name(&self);
}

// 这代码的意思就是 impl 特征 for 结构体, 只要实现了 GetN 特征的结构体, 也同样需要实现 PrintN的特征, 其中 T 这里是泛型且是结构体, 且由于 我们在下面代码实现了 PrintN特征中的print_name函数的默认实现
// 另一种理解: 为实现 GetN 的泛型T, 实现PrintN
impl<T:GetN> PrintN for T{
    fn print_name(&self){
        println!("我叫: {}", &self.get_name())
    }
}

// 定义结构体
struct Stu1{
    name:String
}

// 使用特征 对结构体方法进行约束
impl GetN for Stu1{
    fn get_name(&self) -> &String{
        &self.name
    }
}
let s1 = Stu1{name:"小山".to_string()};
s1.print_name()  // 由于有了 GetN特征, 这里也就有了 PrintN 的特征, 且PrintN中的print_name方法我们有默认实现

trait的专属方法

Trait 也可以看做一个类型

// trait 专属方法, 使用 impl dyn trait {} 定义, 调用时使用 Trait::Method 进行调用(类似关联函数)
trait Say{
    fn say(&self){println!("say")}
}
impl dyn Say {  // 定义 Say 的 trait 专有方法
    fn say_hello() {
        println!("say_hello");
    }
}

struct P;
impl Say for P{};

P.say();
// P.say_hello()  // 无法调用, 因为 say_hello 是 Say trait 的方法,
Say::say_hello();  // 需要使用 trait::method() 的方式调用

let dyn_p = &P as &dyn Say;
// dyn_p.say_hello()  // 显然 trait object 无法调用

生命周期

生命周期参数是为了帮助借用检查器验证非法借用, 防止垂悬指针; 函数间传入和返回的借用必须相关联, 当然单独返回标注 'static 的字面量可以认为是处于该函数栈的底部

生命周期说白了就是作用域的名字, 每一个引用以及包含引用的数据结构, 都要有一个生命周期来指定它保持有效的作用域

出现的原因

下面的函数编译报错是由于 返回值是一个引用, 编译器自动推导他的生命周期是 参数x的声明周期, 但是返回的有可能是x也有可能是y

fn gt_lt(x:& str, y:& str) -> &str {  // error: expected named lifetime parameter
    if x.len() > y.len() {
        x
    }else {
        y
    }
};

显式生命周期

函数中的参数的显式生命周期, fn function<'a >(x:&'a str, y:&'a str), 在函数名后<> 声明, 保证返回的值不会变成悬垂引用, 让编译器知道 传出参数的生命周期同传入的参数的生命周期,比函数体内的长不会造成悬垂引用

fn gt_lt<'a>(x:&'a str, y:&'a str) -> &'a str {  // 这里参数 y 其实也可以不声明生命周期
    if x.len() > y.len() {
        x
    }else {
        y
    }
};
let str = gt_lt("hello", "hh");
println!("比较大小后: {}", &str);

生命周期始终和引用借用相关, 返回值的生命周期参数需要与一个参数的生命周期参数相匹配,如果没有匹配上那么就会造成垂悬引用,解决方案是返回一个有所有权的数据类型而不是一个引用

// fn get_str<'a>(a:&str)->&'a str{ // error: returns a value referencing data owned by the current function  垂悬引用,因为传进来的参数和返回值没有关系,
//     let ret = String::from("really long string");
//     ret.as_str()
// };
fn get_i32()->i32 { 2 }  // 一个返回所有权的函数

结构体的生命周期

如果用到了引用借用类型的参数, 则需要声明生命周期

struct A{ name:&str } // error: expected named lifetime parameter
struct A<'a>{
    name:&'a str  // 这个结构体存在一个字段 是引用的类型 所以必须在结构体名称后面使用尖括号 <> 来进行声明
};

let zs = "zs".to_string();
let a = A{ name: &zs};
// zs;  // 禁止被 let _ = zs, 被drop, 因为后续还在使用 结构体的实例
// let c = zs;  // 同样也禁止转移所有权, 这里发生 "冻结", 在引用借用期间, 禁止转移所有权
println!("结构体如果使用了引用类型的参数需要 'a 来声明生命周期 a.neme:&str = {}", a.name);

结构体实例的生命周期应短于或等于任意一个成员的生命周期(不允许结构体实例存在的时候, 成员被析构了)

#[derive(Debug)]
struct Stu<'a>{name:&'a String}
let mut s = "小明".to_string();
let mut s1 = Stu { name: &s };
// {
//     s;  // 这里让 s 提前drop, 会导致编译不通过  Error: move out of `s` occurs here
// }
// s = "小王".to_string(); // Error: assignment to borrowed `s` occurs here 这里s在上方已经被借用了, 所以无法修改了
println!("s1: {:?}",s1);

生命周期的省略

省略可以用于 impl 和 fn 代码块,如下规则可以不用显式的声明生命周期

1.每个引用的参数都要有他的生命周期,所以每个省略的生命周期参数 都会隐性添加生命周期

fn get_3_1<'x, 'y>(a:&'x String, b:&'y String)->&'x String{a}
fn get_3_1_<'x>(a:&'x String, b:& String)->&'x String{a}  // 这里因为没有返回 b 相关的,所以可以省略 b生命周期

2.如果只有一个输入生命周期参数, 那么它被赋予所有的输出生命周期

// 只有一个参数 编译器会推导函数为: fn get_str<'a>(a:&'a str)->&'a str{ a }
fn get_3_2(a:&str) -> &str{ a }

3.如果在方法中有多个输入生命周期参数, 如果 该方法中有 &self/self 那么 &self/self 的生命周期被赋予到该方法的所有输出的生命周期 如下

方法中的生命周期

struct Student<'a>{
    name:&'a String,
}
impl<'c> Student<'c>{  // 这里'c 只是对应属于结构体中 name 的生命周期
    // fn get_str(& self, s:& str) -> & str {  // 这里满足生命周期省略的第三条规定 返回的 &str 的声明周期对应的是 self 的生命周期
    fn get_str<'b>(&'b self, s:& str) -> &'b str {  // 这里是字面量 'static 的生命周期, 他可以 型变为 'b 生命周期
        "get_str"
    }

    // fn get_str_s(& self, s:& str) -> & str {  // 不写生命周期, 会把生命周期省略第三条套用, 把self 'c的生命周期套用到所有返回值, 但是由于s 的生命周期他并不是'c, 他是编译器自己隐性添加的'x, 导致了冲突
    //     s
    // }

    fn get_str_(s:&str) -> &str{  // 满足了第二条省略规则(可能也只是 字面量的 'static生命周期可以型变为任意比之相短的生命周期)
        "get_str_"
    }
}

let s1 = Student { name: & "zs".to_string() };
println!("get_str: {}", s1.get_str("hh"));
println!("get_str_: {}", Student::get_str_("hh"));

静态生命周期

使用 'static 定义, 存活在整个程序期间, 所有的字符字面值默认都有 'static 生命周期: let s:& 'static str = "hh"

'static 说明这个变量生命周期和和运行程序一样长

对某一类型的'static约束不是用来控制这个类型的对象可以存活多久; 'static约束控制的是这个类型的对象所包含的引用类型所允许的生命周期

以下来自gpt:

不是用来控制类型的对象存活时间: 这意味着标注一个类型(特别是含有引用的类型)为需要 'static 生命周期, 并不直接限制这个类型实例本身的生存期, 一个类型的实例(对象)可以按照其所在的上下文和所有权规则自由地创建和销毁, 不论是否带有 'static 生命周期约束

控制所包含引用类型的生命周期: 关键在于, 如果一个类型内部包含引用(如 &str、&[u8] 或者是指向堆上数据的智能指针) 那么对该类型施加 'static 生命周期约束, 实际上是要求这些内部引用所指向的数据必须具有 'static 生命周期, 换句话说, 这意味着任何这样的引用必须指向的数据是从程序开始就存在并且直到程序结束都不会被销毁的, 例如, 字符串字面量和编译时常量就满足 'static 生命周期, 因为它们存储在程序的静态区域, 程序运行期间始终可用

let  c:& 'static str  = "2233";  // 默认的生命周期
let  c:& str  = "2233";  // 默认添加了 'static 生命周期

泛型, trait bounds 和生命周期

use std::fmt::Display;
fn func<'x, T:Display>(a:&'x str, b:&'x str, c:T) -> &'x str{  // 这里是引用类型, 手动指定的生命周期
    if a.len() < b.len(){
        a
    }else {
        b
    }
}
let  c = "2233";
let r = func("hh", "hei", c);
println!("r: {}", r);

多生命周期标注

fn gt_lt_1<'a, 'b>(a:&'a str, b:&'b str) -> &'a str{  // 因为编译器无法判断这两个生命周期参数的大小,此时可以显式地指定'a和'b的关系
    if a.len() > b.len(){a}else { b }
}
// 这个函数是无法编译的

a:'b 的意思就是 'a'b的子集, 所以返回的生命周期参数是 'b的同时 也满足了 'a(可以看成一个集合 b包含了a, 或者oop思想a继承自b), 'a'b标注告诉编译器, 'a活的更长, 活的更长就意味着 'b能干的 'a 一样能干

fn gt_lt_1<'a:'b, 'b>(a:&'a String, b:&'b String) -> &'b str{  // 返回一个较短的生命周期
    if a.len() > b.len(){
        a
    }else {
        b
    }
}

let s1 = "hi".to_string();
let s2 = "hello".to_string();
let s3 = gt_lt_1(&s1, &s2);
println!("经过比较 s3: {}", s3);
{s1;} // 这里提前drop掉 s1 也不会影响, 因为生命周期声明是帮助编译器检查参数的一切关系的, 对外发生的东西不管

生命周期缺陷

#[derive(Debug)]
struct Foo;
impl Foo{
    fn mutate(&mut self)->&Self{&*self}  // 发生了 reborrow 生命被拉长 到外部
    fn share(&self){}
}

let mut foo = Foo;
let loan = foo.mutate();  // mutable borrow occurs here
foo.share();  // 不可变借用发生在这里
// let x = loan;  // error: mutable borrow later used here,  这里无法编译, 明明 loan 是不可变引用,但是这里却变成了可变引用, 还是用的mutate方法中的 &mut self的生命周期
let mut v = 123;
let v1 = &mut v;
let v2 = &*v1;
v;
let v3 = v2;

// 以下来自gpt
// 在 Rust 中, 可变引用 &mut T 表示你有独占访问某个数据的权利, 以进行修改, 当你创建一个可变引用 v1 = &mut v, Rust 保证在此引用存在期间, 不会有其他引用(无论是可变还是不可变)能够访问同一数据, 以此来避免数据竞争和不一致的状态, 
// 当你执行 let v2 = &*v1; 这一行时, 实际上进行了以下操作:
// *v1 解引用 v1, 得到了对 v 的直接访问, 但由于 v1 是可变引用, 这个访问仍然是独占的, 
// & 接着对解引用的结果取不可变引用, 生成了 v2,
// 虽然 v2 是一个不可变引用, 但因为它是通过对可变引用 v1 解引用后创建的, 所以 Rust 的借用规则认为 v1 的独占借用状态仍然有效, 这意味着, 只要 v1 存在, 即使你已经有了一个不可变引用 v2, 也无法再创建新的引用(无论是可变还是不可变)指向 v, 或者直接使用 v, 因为这可能会违反内存安全原则, 
// 简单来说, v1 的有效性不仅仅是因为它本身是个引用, 还因为它代表了对 v 的独占访问权, 这个权利直到 v1 超出作用域才会被释放, 创建 v2 并没有改变 v1 的独占借用状态, 因此对 v 的其他访问依然受限, 这就是为什么说“没有改变 v1 仍然有效的事实”, 

本人的推断

// 好像生命周期缺陷只有在reborrow的时候遇到?
// 因为 可变的引用一直存在, 所以 禁止 使用创建v的引用和使用v?
// 自始至终 都是可变引用在存活, 一直都在独占的使用
let mut v = 123;
let v1 = &mut v;
let v2 = &*v1;  // reborrow

... 中间一直在独占使用, 只要 v1 存在 就禁止使用v, 以及禁止重新借用

let v3 = v2;    // 此句之后 可变引用的 所有权返回到 v1
println!("{}", v1);

闭包

闭包会自动生成一个匿名结构体, 通过Trait调用的, 闭包等价于一个实现了特定Trait(Fn)的结构体

! 闭包和所有权语义具有一致性, 息息相关, 生成的结构体对应实现 相关Trait(Fn, FnMut, FnOnce)

闭包会创建新的作用域, 对于环境变量默认使用不可变借用来捕获所有权,其次是可变借用,最后move

闭包根据内部如何使用外部自由变量来决定是否 copy move borrow

定义闭包

编译器可以推导闭包返回类型

let func1 = | x:u32 | -> u32 { x + 1};
let func2 = | x:u32 | -> u32 { x };
let func3 = | x:u32 | x;  // 推导出了返回类型

闭包内部是一个封闭的环境

let mut flag: i32 = 1;
let add_1 = |x: i32| x + flag;  // 这里生成了一个闭包结构体 其内包含了需要使用到的变量
// flag = 3; // error: assignment to borrowed `flag` occurs here  上面的那一行闭包中借用了 flag, 所以后面不允许修改(可以通过添加 move 关键字来强制 COPY)
let flag = 2;  // 这是let重新申请了一个位置, 变量覆盖了, 封闭环境再次修改这个变量, 影响不到闭包函数中
let n: i32 = 3;
let n_1 = add_1(n);
println!("n_1 : {}", n_1);

包含闭包的结构体

创建一个结构体其中需要有回调函数和值, 回调函数通过 trait bound语法 添加特征约束 为 Fn(u32) -> u32

// 一个具有缓存功能的结构体
struct Cacher<T>
    where T: Fn(u32) -> u32
{
    callback:T,
    value:Option<u32>
};

impl<T> Cacher<T>
    where T:Fn(u32) -> u32
{
    fn new(callback:T) -> Cacher<T> {  // 关联函数, 创建一个实例, 用来实现缓存功能, 新的实例 value为None
        Cacher {
            callback,
            value:None
        }
    }

    fn value(&mut self, args:u32) -> u32 {  // 实例方法, 如果已经有了value 则返回现有value,  如果第一次调用没有value走向None分支 使外部传进来的args参数调用回调函数返回外部
        match self.value {
            Some(V) => V,
            None => {
                let v = (self.callback)(args);
                self.value = Some(v);
                v
            }
        }
    }
}

闭包的变量捕获

创建一个闭包时 Rust自动推导如何使用外部变量,所有闭包都可以被调用至少一次, 因此所有闭包都实现了 FnOnce

闭包获取变量的三种方式 (1. 获取所有权FnOnce:获取所有权; 2. 可变借用FnMut:获取可变的借用值所以可以改变环境; 3. 不可变借用Fn)

// == 号的实现 使用的是 &self 捕获的是不可变借用, 所以这里没有发生所有权转移
let z = String::from("hello");
let fn1 = |f: String| {f == z};
fn1("123".to_string());
println!("{}",z);  // 所有权没有转移
// == 号的实现 由于使用的是 &self, 捕获的是不可变借用, 所以不会捕获外部的所有权
let x = vec![1,2,3];
let fn2 = | f | f == x;
fn2(vec![1,2,3]);
println!("{:?}",x);  // 所有权没有转移

move关键字

闭包通过 move 关键字可以对环境中的 自由变量 做出后面的操作: 对于 copy 类型的变量, 如果使用 move关键字 则会使用使用变量的Copy的新变量, 而 对于移动语义的变量则会强制执行获取所有权

// 使用 move 强制手动转移所有权
let x = vec![9,8,7];
let fn2 =  move | f | f == x;
fn2(vec![9,8,7]);
// println!("{:?}",x)  // 这里由于闭包使用了 move 所以所有权已经转移了 error: value borrowed here after move

闭包中的移动语义

会捕获其所有权

let s = String::from("hello");
let f1 = || { s; };  // 后续无法再使用变量 s 因为内部是 let _ = s;
f1();  // Fnonce
// println!("{}",s);  // Error: value borrowed here after move
// f1();  // 无法再次使用 Error: value used here after move


let mut ss = "hello".to_string();
let mut f2 = || { let a = ss; };
f2();
let f3 = f2;;  // 无法使用了 提示f2 因为调用发生了移动, 所以这也是 FnOnce的

FnMut

闭包内的捕获的数据发生了变化 所以需要 fn指针 本身需要是可变的

let mut ss = "hello".to_string();
let mut f2 = || { ss+="123"; };
f2();
f2();

逃逸性

逃逸闭包必须复制或移动环境变量. 这是很显然的, 如果闭包在词法作用域之外使用, 而其如果以引用的方式获取环境变量, 有可能引起悬垂指针问题

能为函数返回, 不再函数调用中被销毁的闭包, 就叫做逃逸闭包, 后面的栏目会有专门的讲解

closure 在捕获外部变量的时候,如果外部变量是引用类型,或者外部变量内部包含引用类型, 或者闭包内捕获的是外部的不可变引用, 如果闭包在词法作用域之外使用(比如多线程中), 会造成垂悬指针的问题

fn test()->Box<dyn Fn() -> ()>{
    // 1
    let x = 123;
    let c: Box<dyn Fn() -> ()> = Box::new(|| {
        let d = x;
        x; 
    });  // 这里 闭包捕获 的是外部变量的copy 因为i32 实现了 Copy trait 所以这里

    // 2
    let x = &123;
    let c: Box<dyn Fn()> = Box::new(|| {
        let d = x;
        x; 
    });  // 这里 闭包捕获 的是外部变量 不可变引用 的copy   因为 &123(不可变借用)实现了Copy trait

    // 3
    let x = "123";
    let c: Box<dyn Fn()> = Box::new(|| {
        let d = x;
        x; 
    });  // 字面量的, &str本身是一个不可变引用, 这里 闭包使用 &str, 是字面量的不可变引用的copy

    // 4
    let s = "123".to_string();
    let x = &s;
    let c: Box<dyn Fn()> = Box::new(move || {
        let d = x;
        x;
    });  // 这里 闭包使用 &String 不可变借用, 不可变借拥有 Coyt trait, 所以闭包这里捕获的是x的值而不是所有权, 而s本身就没有被捕获
    println!("s 依然可以使用: :?{}", s);


    let x = "123".to_string();  // 这个例子不在讨论之中,只是乱入的,只是解释一下:这里变量x 在词法作用域结束之后已经被x 被 drop 了,因为 x 没有实现Copy, x变成了垂悬指针
    // let c: Box<dyn Fn()> = Box::new(move || { x; });  // TODO 奇怪这样编译不过 (21年10月27), 因为闭包内部捕获了所有权且被消耗了这个变量的类型应该是FnOnce, 而我们的签名是Fn, 类型都对不上了, 这样编译不过是因为 捕获到了环境变量, 在内部被消耗掉了,只能调用1次 但是我们的签名时Fn换成FnOnce即可编译通过
    // let c = Box::new(move || { x; });  // TODO 这样就可以编译过, 让编译器自动推导为FnOnce


    // 闭包根据内部如何使用外部自由变量来决定是否 copy move borrow
    let s1 = "123".to_string();
    let f1 = || { let x = s1; };
    // let c1 = s1;  // 编译是不通过的, 内部发生了move, 捕获的也是move

    let s2 = "123".to_string();
    let f2 = || { let x = &s2; };
    let c2 = s2;

    let s3 = "123".to_string();
    let f3 = move || { let x = &s3; };  // 强制转移所有权, 因为是在闭包内使用的&s3, 这里先move的s3后使用的不可变引用, 他和上面第四个例子捕获不可变引用 又不太一样, 
    // let c3 = s3;  // 编译是不通过的


    // // 使用 move 强制执行 Copy或Move
    let x = "123";
    let c: Box<dyn Fn()> = Box::new(move || { x; });
    return c;
    // 这之后, 此词法作用域之内的变量被 drop, 但是x是字面量, 他是 'static 的, 所以也不会drop掉
};

let f1 = test();
f1();

下面的编译会报错 如果使用 FnMut: move occurs because s has type std::string::String, which does not implement the Copy trait

// 创建的变量 s 是保存在 f_mut中的 会因为 let _ = s; 而转移所有权, 从而导致闭包变为FnOnce
fn f_mut() -> impl for<'a> FnOnce(&'a str) -> String {  // 出现了闭包逃逸 FnOnce正常编译, 但是只能调用1次
// fn f_mut() -> impl for<'a> FnMut(&'a str) -> String {  // FnMut无法编译, s 没有实现copy, FnMut这个Trait编译器认为你可能会调用多次, 这是为了防止你多次调用
    let mut s = "1234".to_string();
    move |i| {s += i; s}   // s返回到外部
}

let f = f_mut();
let s_1 = f("123");  // 闭包内部的所有权已经转移到这里了
// f("123");  // 使用 FnOnce, Error 无法调用第二次

返回闭包

闭包作为返回值返回必须使用 trait 对象才能返回

使用impl 可以对返回的变量进行约束, 但 impl返回的只是类型占位 , 是非常局限的只支持单分支的代码,如果出现分支 由于impl 静态的,出现多分支的时候会出现闭包类型不一样

// 无分支的返回
fn get_fn1() -> impl Fn(i32,i32)->i32{
    let x = |x, y| x + y;
    x
}
// 多分支的返回 Error: expected closure, found a different closure
// 那怕两个函数是一样的代码, 但是他也是两个实现了 Fn trait 的不同的类型
fn get_fn3(flag:i32) -> impl Fn() -> (){
    if true{
        return move ||{flag;}
    }
    else {
        return move ||{flag;}
    }
}

使用trait对象Box<dyn Fn()> 返回闭包到外部

fn get_fn2()-> Box<dyn Fn(i32,i32)-> i32>{
    let x = |x, y| {x + y};
    Box::new(x)
}
let function = get_fn2();

迭代器

迭代器负责遍历序列中的每一项和决定序列何时结束的逻辑

迭代器创建之后是惰性的, 在调用之前不会有任何效果

每个迭代器都实现了 iterator trait, 定义在标准库中, (next 是被要求实现的唯一的一个方法, next一次返回一个元素,当迭代器结束的时候 返回None)

每个实现了 iterator trait 的类型, 也实现了 IntoIterator (impl<T:iterator> IntoIterator for T)

trait Iterator{
    type Item;
    fn next(&mut self) -> Option<Self::Item>;  // type Item 和  Self::Item 这种用法叫做定义 trait 的关联类型
}

迭代器常见的三种: 1. IntoIter,转移所有权,对应self(for循环默认的语法糖就是这个); 2. Iter,获得不可变借用,对应&self; 3. IterMut,获得可变借用,对应&mut self

迭代器使用

调用一些容器的 iter方法会使用&self返回Iter::new(self)

let a_iter1 = a.iter();  // 这一步不会对 a 造成任何影响
for i in a_iter1{  // for 调用了 into_iter
    println!("使用迭代器: {}",i)
}

for循环和语法糖

for循环实际上是一个语法糖, for循环真正循环的 是一个迭代器(Iterator), 真正在这个语法糖里起作用的, 是 IntoIterator trait, 内调用了 into_iter 使用了 self的方法, 会获取其所有权

let a = vec![1, 2, 3, 4];
for i in a{ // 如果不用迭代器遍历数据, 则会发生所有权转移 后续无法在使用a
    println!("不用迭代器: {}",i)
}

next方法

Iter struct 中的next方法是返回 不可变引用

let a = vec![1, 2, 3];
let mut a_iter = a.iter();
if let Some(v) = a_iter.next() {
    println!("next1: {}", v)  // 1
}
if let Some(v) = a_iter.next() {
    println!("next1: {}", v)  // 2
}
if let Some(v) = a_iter.next() {
    println!("next1: {}", v)  // 3
}
if let Some(v) = a_iter.next() {
    println!("next1: {}", v)  // None
}else {
    println!("已经空了: None")
}

迭代可变引用

使用iter_mut返回IterMut struct

let mut a = vec![1, 2, 3];
let mut a_iter = a.iter_mut();
if let Some(v) = a_iter.next(){
    *v *= 10
}
println!("改变了: {:?}", a);

自定义一个迭代器

// 自定义一个迭代器
struct Counter{  // 定义结构体
    count:u32
};
impl Counter{
    fn new()->Counter{
        Counter{
            count:0
        }
    }
};
impl Iterator for Counter{  // 定义 Iterator trait
    type Item = u32;
    fn next(&mut self)-> Option<Self::Item>{
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        }else {
            None
        }
    }
}

let mut counter = Counter::new();  // 生成实例
for i in 0..10 {
    if let Some(v) = counter.next(){
        println!("实现的next方法, 实例调用 next 方法: {}",i)
    }else {
        println!("实现的next方法, 调用完毕");
        break
    }
}

迭代器常用方法

查看迭代器边界信息

使用 size_hint 方法获取一个迭代器状态, 返回一个元组内有两个元素: 一个元素表示下限(lower bound),第二个元素表示上限(upper bound)

next 会同时改变上限和下限

let v = [1, 2, 3];
let mut v = v.iter();
v.next();
v.next();
println!("{:?}", v.size_hint());  // (1, Some(1))  表示元素还剩1个元素
v.next();
println!("{:?}", v.size_hint());  // (0, Some(0))

一个无限的迭代器没有上限, 以及一个最大的下限

// Range 类型也实现了 iterator trait
println!("{:?}", (1..).size_hint());  // (18446744073709551615, None)

使用 filter 根据回调,返回回调为True的filter, 也会消耗迭代器本身, 不会消耗上限

let iter = (0..10).filter(|x|x & 2 == 2);
println!("使用filter 不会消耗上限: {:?}", iter.size_hint());  // (0, Some(10))

使用迭代器追加字符串

let mut s = "hello".to_string();
s.extend(vec!["1", "2"]);
s.extend("2233".chars());
println!("字符串增加后: {}", s);

迭代器返回指定下标元素

使用 nth方法 获得指定下标的元素返回的是一个Option(注意此方法会消费迭代器指定下标元素之前的迭代器)

let v = vec![2, 3, 1, 45];
let mut v_iter = v.into_iter();
let idx_2 = v_iter.nth(2);
println!("idx_2: {:?}",idx_2);

sum

对迭代器求和

let v1 = vec![1,2,3];
let mut v1_iter = v1.iter();
let sum:u32 = v1_iter.sum();  // 调用消费适配器, sum会使用self 获得所有权
println!("调用消费适配器之后求和为: {}",sum);
// if let Some(v) = v1_iter.next() {  // 这里直接所有权被移走了
//     println!("{}", v)
// } else { println!("调用消费适配器之后,迭代器内已经没有值了 {:?}", v1_iter) };  // 不会走到这一步因为所有权都已经被移走了

映射 map

惰性执行 接收一个闭包, 对迭代器内的每个元素进行调用, 注意! map 是惰性执行的, 在没有next之前是不会执行的

let v1 = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
println!("迭代适配器 映射 之后的值: {:?}", v2);

过滤 filter

惰性执行, 过滤(接收一个闭包, 元素传入闭包中,获得返回 True 的元素)

let v1 = vec![1, 2, 3];
let v2: Vec<_> = v1.into_iter().filter(|x| *x < 3 ).collect();
println!("迭代适配器 过滤 之后的值: {:?}", v2);

filter_map

惰性执行 创建一个迭代器, 先执行filter 再执行map, 它在每个元素上调用这个闭包, 如果闭包返回一些(元素), 则返回该元素, 如果闭包返回None则跳过该元素, 尝试下个元素

filter 有局限性, filter 返回的是bool值, 如果想取值, 需要在 .map 一下, filter_map 类似 filter().map()

let mut v = [1, 2, 3, 4, 5, 6];
let mut i = v.iter().filter_map(|&x| {if x % 2 ==0 {return Some(x)};None});
for c in i{
    println!("filtermap 进行联合过滤: {:?}",c)  // 2  4  6
}

peekable

创建一个迭代器, 它可以使用peek查看迭代器的下一个元素而不消耗迭代器

let mut v = [1, 2, 3, 4, 5].iter().peekable();
println!("peek: {:?}", v.peek());
println!("next: {:?}", v.next());  // 这里依然 是 Some(1)

skip_while

接收一个闭包, 跳过对闭包返回 true 的元素, 注意!: 闭包在遇到 返回 false 的情况则停止闭包调用, 剩下的元素不再判断

let v = [-1, -2, 4, -7].iter().skip_while(|x| **x < 0);
for i in v{
    println!("skip_while 后的结果: {:?}",i)  // 4, -7 遇到4之后后面的不再判断了
}

take

获取指定位置 之前 的元素(不包括指定位置的值), 如果取出的值超过最大元素数量,则只会取到末尾

let v = ['a', 'b', 'c', 'd'];
let i = v.iter().take(3);
for c in i {
    println!("使用 take 取之前的值: {:?}", c)  // 'a'  'b'  'c'
};

接上面的take 的使用这里有一点疑问, 为啥我这里实现的示例 &mut T 调用使用 self 的方法, 会转移 T的所有权(& mut iter.take(self n) 不会转移iter的所有权, & mut String.into(self) 会转移String的所有权) 两个method 同样使用了self 为何一个转移一个没有转移 对疑问的释疑: 上面的take 是 &mut iter 其本身的 take, 而不是 iter 的take &T& mut T 也是和T一样属于独立的类型, 查看源码得知, impl<I: Iterator + ?Sized> Iterator for &mut I {} 这里 所有实现Iterator的 T他的 &mut T也实现了Iterator

scan

一个类似于fold的迭代器适配器, 它保存内部状态并生成一个新的迭代器(接收两个参数, 一个初始的状态初始值, 一个闭包), 迭代器将生成来自闭包的返回值成为一个新的迭代器

let v = [1, 2, 3, 4];
let i = v.iter().scan(1, |status, x| {
    *status = *status * x;  // 修改状态
    Some(*status)  // 返回外部
});
for c in i{
    println!("scan 为每个元素执行闭包, 并保存相应的状态后: {}", c)  // 1 2 6 24
}

flatten

消除迭代器的中间层

let v = vec![vec![2, 3], vec![9]];
for i in v.iter().flatten(){
    println!("flatten: 消除中间层之后: {:?}",i)
}
// 多次调用,可以消除多层
let d3 = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]];

let d2 = d3.iter().flatten().collect::<Vec<_>>();
assert_eq!(d2, [&[1, 2], &[3, 4], &[5, 6], &[7, 8]]);

let d1 = d3.iter().flatten().flatten().collect::<Vec<_>>();
assert_eq!(d1, [&1, &2, &3, &4, &5, &6, &7, &8]);

// 17. flat_map 消除迭代器中间层之前, 为每个元素执行一次map操作, 类似 map(f).flatten()
let v = vec!["abc", "def"];
let i = v.iter().flat_map(|x| x.chars());

for c in i{
    println!("flat_map: {}", c);
}

by_ref

使用 by_ref 可以不转移所有权 调用消费器

let mut v = [1, 2, 3, 4].iter();
let sum = v.take(2).fold(0, |status, x| status + x);
println!("一些消费器会转移其所有权,有时候我们不希望被转移所有权: {}", sum);  // 3
// println!("这里无法调用, 所有权已经被转移了:{:?}", v.next());

let mut v = [1, 2, 3, 4].iter();
let v_ref = v.by_ref();
let v_ref= &mut v;
let sum = v_ref.take(2).fold(0, |status, x| status + x);
println!("使用sum 下面依然可以调用: {}", sum);  // 3
println!("依然可以调用: {:?}",v.next());  // Some(3)

partition

根据闭包的规则划分两个新的 迭代器, 一个是闭包返回 true 一个是闭包返回 false 的

let v = [1, 2, 3, 4, 5];
let (even, odd):(Vec<i32>,Vec<i32>) = v.iter().partition(|x| **x % 2 == 0);
println!("使用 partition 分离之后 偶数为: {:?}", even);  // [2, 4]
println!("使用 partition 分离之后 奇数为: {:?}", odd);  // [1, 3, 5]

翻转 rev

let lis = vec![1, 2, 3];
// lis.iter().next_back();
let lis_back = lis.iter().rev();  // 得到一个翻转的迭代器

查找是否存在某元素 any

let v = vec![1, 2, 3, 4, 5];
let mut v_iter = v.iter();
println!("查找容器中是否存在满足条件的元素返回bool: {}", v_iter.any(|&x| x == 2));

累加器 fold

let v = vec![1, 2, 3, 4, 5];
let mut v_iter = v.iter();
println!("第一个为初始值,第二个为带有两个参数的闭包, 其中闭包的第一个参数被称为累加器: {}", v_iter.fold(100, |acc, x| acc + x));

收集器 collect

collect(使用时需要显示添加收集后类型);collect消费器有“收集”功能,在语义上可以理解为将迭代器中的元素收集到指定的集合容器中

let v = vec![1, 2, 3, 4, 5];
let mut v_iter = v.iter();
let mut new_iter = v_iter.map(|x| x * x);  // 加法操作会自动解引用
// let new_vec:Vec<i32> = new_iter.collect();  // 定义变量时 使用类型注解
let new_vec = new_iter.collect::<Vec<i32>>();  // 显式的添加类型
println!("显式的添加类型, 使迭代器转为一个Vec: {:?}",new_vec);

计算迭代次数 count

消耗迭代器, 计算迭代次数并返回

let v = [1, 2, 3, 4];
let mut v = v.iter();
println!("count 方法: {:?}", v.count());  // count 方法: 4

最后一个元素 last

消耗迭代器, 并返回最有一个元素(这个方法将对迭代器迭代, 直到它返回None, 在None返回之后, last()将返回它看到的最后一个元素, )

let v = [1, 2, 3];
let mut v = v.iter();
let last = v.last();
println!("使用last 方法获取的最后一个元素为: {:?}",last);  // 3

返回第n个元素 nth

返回迭代器的第n个元素, 此方法,会消耗之前的元素

let v = [1, 2, 3, 4];
let mut v = v.iter();
let nth_test = v.nth(2);  // 得到下标为2个元素
println!("元素: {:?}, 迭代器状态: {:?}", nth_test, v.size_hint()); // 元素: Some(3), 迭代器状态: (1, Some(1))

根据步长返回新的迭代器 step_by

返回一个新的迭代器,根据步长 依次跳跃, 不管给定的步长是什么, 迭代器的第一个元素都将被返回

let v = [1, 2, 3, 1, 123, 23];
let mut v = v.iter().step_by(2);
println!("step_by 根据步长进行返回: {:?}", v.next());  // 1
println!("step_by 根据步长进行返回: {:?}", v.next());  // 3
println!("step_by 根据步长进行返回: {:?}", v.next());  // 123

链接两个迭代器 chain

链接两个迭代器, 将返回一个新的迭代器, 它将首先遍历第一个迭代器的值, 然后遍历第二个迭代器的值, 注意 这会转移两个旧迭代器所有权

let v1 = [1, 2, 3].iter();
let v2 = [2, 3, 4].iter();
let i = v1.chain(v2);
println!("chain 新的迭代器状态为: {:?}", i.size_hint());  // chain 新的迭代器状态为: (6, Some(6))

由于chain()的参数需要符合IntoIter trait, 所以我们可以传递任何可以转换为迭代器的内容, 而不仅仅是迭代器本身, 例如, slices (&[T])实现了IntoIter, 因此可以直接传递给chain()

let v1 = &[1, 2];
let v2 = &[2, 2, 4, 4, 5];
let mut i = v1.iter().chain(v2);
println!("chain 链接 slice &[T] 状态为: {:?}", i.size_hint());  // chain 链接 slice &[T] 状态为: (7, Some(7))

压缩迭代器 zip

将两个迭代器压缩到一个迭代器中, 迭代其他两个迭代器, 返回一个元组, 其中第一个元素来自第一个迭代器, 第二个元素来自第二个迭代器, 一个迭代器返回None, zip将短路, 返回 None

let v1 = [2, 2, 3, 4];
let v2 = ['a', 'b', 'c', 'd'];
let mut i = v1.iter().zip(v2.iter());
println!("zip 将两个迭代器进行叠加状态为: {:?}, next的值为: {:?}", i.size_hint(), i.next());
// zip 将两个迭代器进行叠加状态为: (4, Some(4)), next的值为: Some((2, 'a'))

for_each

主动执行 迭代器的每个元素调用闭包, 类似for循环, 但是比 for 循环要快

let v = [1, 2, 3];
v.iter().for_each(|x|println!("for_each 会为每个元素调用闭包: {}",x));  // 1 2 3

fold_first

与 fold 相同, 只不过使用 迭代器的第一个元素为初始值, 这是一个 unstable library

all

根据闭包调用,如果每个元素调用后都返回 true 那么 all 也就是 true

any

根据闭包调用,如果其内有一个元素返回了 true 那么 any 也就是 true

find

根据闭包调用, 返回第一个结果 为 true 的元素

position

根据闭包调用, 返回第一个结果为 true 的下标

eq

判断两个迭代器 其内的元素 是否完全相等

assert_eq!([2,5].iter().eq([2,5].iter()), true);  // index 对应的元素也必须相等

ne

判断两个迭代器内的元素是否不相等

lt

判断第一个迭代器内的 元素相加 小于 第二个迭代器

assert_eq!([1,3].iter().lt([1,6,3].iter()), true);  // 1+3 是小于 1+6+3的

le/gt/ge

同上

自定义类型实现FromIterator

自定义集合实现FromIterator trait, 以支持使用 collect

use std::iter::FromIterator;
#[derive(Debug)]
struct MyVec(Vec<i32>);  // 创建结构体

// 定义结构体方法
impl MyVec{
    fn new()->MyVec{
        MyVec(Vec::new())
    }
    fn push(&mut self, value:i32) -> (){
        self.0.push(value)
    }
}
// 实现FromIterator
impl FromIterator<i32> for MyVec{
    fn from_iter<I>(iter:I) -> Self  // 接收一个泛型
    where I:IntoIterator<Item = i32>  // 其泛型必须满足具体的特征约束
    {
        let mut c = MyVec::new();
        for i in iter{
            c.push(i)
        };
        c
    }
}

// 使用 MyVec::from_iter
let iter = vec![1, 2, 3, 4].into_iter();
let mut my_vec = MyVec::from_iter(iter);
println!("my_vec: {:?}", my_vec);
// 使用 collect
let iter = vec![2, 2, 3, 4].into_iter();
let mut my_vec:MyVec = iter.collect();
println!("my_vec: {:?}", my_vec);
// 使用 collect
let iter = vec![3, 2, 3, 4].into_iter();
let mut my_vec:MyVec = iter.collect::<MyVec>();
println!("my_vec: {:?}", my_vec);

智能指针

指针是一个包含内存地址的变量, 数据保存在堆上, 而栈上保存的是 数据 在堆上的地址, 除了数据被存储在堆上以外,Box没有任何性能损失

对于Box<T>类型来说,如果包含的类型T属于复制语义,则执行按位复制;如果属于移动语义,则移动所有权, 这样做表示 新的Box变量已经和原变量没有任何关系了

智能指针是一种数据结构, 类似于指针, 但是却有额外的信息,比如有一些引用计数, 当智能指针没有任何所有者的时候会drop

普通引用和智能指针的区别: 引用只是借用数据的指针, 而智能指针则是拥有他们指向的数据

智能指针一般使用结构体实现, 其实现了 Deref(解引用操作的相关操作) 和 Drop(允许自定义智能指针离开作用与执行的代码) 这两个 trait

drop 标志储存在栈中, 并不在实现 Drop 的类型里

适用场景:当有一个未知大小类型比如String,但是上下文中使用这个变量又需要知道大小, 当有大量数据希望不copy的情况下转移所有权, 当希望有一个值只关心他的类型是否实现了特定trait并不关心具体类型的时候

Box<T>使用操作符(*)进行解引用,对于未实现Copy trait 的类型, 可能会出现转移所有权的行为, Rc<T>和Arc<T>不支持解引用移动

基本使用

智能指针拥有继承自 Box<T> T 变量中的方法

let a = Box::from(1);

println!("BOX a.to_string(): {}", a.to_string());  // 直接调用a的方法

一个Box智能指针实现的链表

需要解决的问题

定义一个链表 递归类型 这里在使用的会后会出现问题,因为无法知道 list占用的大小

enum List {  // 
    Cons(i32,List),
    Nil
}

使用Box定义一个链表

enum List {  // 定义一个链表 递归类型 使用 Box<T> 可以进行递归 得到大小,因为 Box<T>存储的是指针,指针又在栈上,任何 List 值最多需要一个i32 加上 box 指针数据的大小
    Cons(i32, Box<List>),
    Nil
}
use List::Cons;
use List::Nil;
// 创建链表, 保存的是栈上的数据(堆上的地址), 所以一开始就是知道栈上大小的,
let list = Cons(2,
                Box::new(Cons(4,
                              Box::new(Cons(5,
                                            Box::new(Nil))))));

Deref

手动实现类似Box

use std::ops::Deref;
struct MyBox<T>(T);  // 创建结构体

impl<T> MyBox<T>
{
    fn new(x:T)->MyBox<T>{
        MyBox(x)
    }
}

实现解引用 "*" 操作, 需要实现实现标准库的 Deref trait 名为 deref 的方法返回 值的引用, 在我们使用 *a 的时候, 内部做的就是 *(a.deref()), 通过调用deref得到引用 再解引用

Rust内将 * 运算符替换为 deref 方法调用和一个普通解引用,我们就不需要调用 deref 方法了

impl<T> Deref for MyBox<T>  // 特征实现
{
    type Target = T;
    fn deref(&self) -> &T {  // 这里使用 &T 不希望解引用的时候拿掉 实例的所有权
        match &self.0 {
            i32=>{
                 println!("发现了一个 i32 类型")
            }
        }
        &self.0  // 这里返回引用,是我们不希望内部值得所有权被移出
    }
}

let x = 2;
let a = MyBox::new(x);
assert_eq!(*a,x);

//  自动解引用
let mut s = MyBox::new("234".to_string());
s.len();

自动Deref

String类型实现 Deref 返回的是 &str, 如果一个类型 T实现了Deref<Target=U>, 则该类型T的引用(或智能指针)在引用的时候会 根据需要 选择 自动转换为类型U

String类型实现的add方法的右值参数必须是&str类型, &s2这里自动是&String类型,会被自动隐式转换为&str

let s1 = "hello".to_string();
let s2 = "world".to_string();
let s3 = s1 + &s2;  // 这里 &String 会被转换为 &str
// let s3 = s1 + &&&*&&*&&&&s2.deref();
let s4 = s3.deref();
let box_a = Box::from(a);  //
assert_eq!(*box_a, a);  // 解引用 智能指针, 由此可见 智能指针指向了 a 的地址

变量 a 是Box<&str>类型,它并没有实现过chars()方法;但是现在可以直接调用,因为Box<T>实现了Deref<Target<T>>, 使用起来完全透明,就好像Box并不存在一样

如果一些方法, 这个变量中没有, 那么该变量会自动解引用为 T 类型

let a = Box::new("hello");
println!("{:?}", a.chars());

解引用多态与可变性交互

如果一个类型 T实现了Deref<Target=U>,则该类型T的引用(或智能指针)在引用的时候会被自动转换为类型U

1.当T: Deref<target=U>时, 从 &T 变成 &U类型的引用, 一个 &TT 实现了 U 类型的 Deref ,则可以直接得到 &U, 比如 String类型的deref得到的就是&str

2.当T: DerefMut<target=U> 时, 从 &mut T 变成 & mut U 的引用

3.当T: Deref<target=U> 时, 从 &mut T 变为 &U, 不可能从 & T 变成 & mut T因为同一个变量只能有一个可变引用

let function_1 = |x: &str| println!("function_1, 解引用多态后: {}", x); // 这里将MyBox 变为MyBox<String>,再将 String的解引用 变成字符串的slice &str,  目标类型: &str, 初始类型: &String 都是不可变的 符合 规则1
let s = MyBox(String::from("hehe"));
function_1(&(*s)[..]);  // 如果没有实现解引用多态时需要这样调用 (*s) 将 MyBox<String> 解引用为 String, [..]得到slice 再使用 & 得到引用
function_1(&s);  // 实现了解引用多态, 可以直接调用

Drop trait

当值离开作用域的时候,执行次函数的代码, drop函数需要传入可变引用, 因为要修改/清空变量内的数据

impl<T> Drop for MyBox<T>{
    fn drop(&mut self){
        // self.count -= 1  上面传入 & mut self 原因就是因为可能会计算并修改实例的引用计数,
        println!("清理了一个变量")
    }
}
{
    println!("----开始声明a变量");
    let a = MyBox(123);
}
println!("----这里a变量已经被清理");

rust 提供了一个 std::mem::drop() 通过传递希望提早强制丢弃的值作为参数, 当然并不是一定会drop(这里 编译器会酌情处理)

struct Student{
    name:String
}

impl Drop for Student{
    fn drop(&mut self){
        println!("被释放了: {}",&self.name)
    }
}

let s = Student { name: "小明".to_string() };
drop(s);  // 提前释放
// println!("生成了一个: {}", s.name);  // error: value borrowed here after move

解引用引发的所有权问题

使用操作符 *进行解引用可能会出现转移所有权的行为

let bs = Box::new(String::from("hello"));
let bi = Box::new(123);

*bs;  // 这一解引用之后所有权已经被转移走 let _ = *bs
*bi;  // bi则不会因为 i32 实现了Copy trait

// println!("bs:{}",bs);  // Error: value borrowed here after partial move
println!("bi:{}",bi)

Rc智能指针

通过RC<T> 允许通过不可变引用来 只读的 在 程序的 多个部分, 共享数据不允许改变, 共享数据 (如果是多个可变引用,会违反借用规则之一,可能会造成数据竞争)

需要解决的问题

如果有多个变量 想同时拥有某保存在堆上数据的所有权, 则需要使用Rc智能指针, 否则 1个堆上数据只能有一个变量拥有其所有权

enum List{
    Cons(i32,Box<List>),
    Nil
};
use List::{Cons, Nil};

let li_1 = Cons(2,
                Box::new(Cons(4,
                              Box::new(Nil))));
let li_3 = Cons(12, Box::new(li_1));
// let li_4 = Cons(29, Box::new(li_1));  // error: value used here after move

Rc智能指针实现的链表

使用 Rc 智能指针重新定义 链表, 并使用 Rc::clone()&self.clone(), 来不转移所有权的情况下 共享数据

推荐使用 Rc::clone, Rc::clone的实现并不像大部分类型的clone 实现那样对所有数据进行深拷贝; Rc::clone 只会增加引用计数,这并不会花费多少时间 (20240620 查看源码好像是一样的了)

enum Li {
    Item(i32, Rc<Li>),
    Null
}
use Li::{Item,Null};
use std::rc::Rc;

let lis_1 = Rc::new(Item(8,
                         Rc::new(Item(17,
                                      Rc::new(Null)))));
let lis_2 = Item(13, Rc::clone(&lis_1));  // 引用计数增加1 变为2
let lis_3 = Item(13, Rc::clone(&lis_1));  // 引用计数增加1 变为3

// 3. 引用计数: 通过 Rc::strong_count 来获取Rc的引用计数
{
    let lis_3 = Item(13, lis_1.clone());
    println!("lis_1的引用计数为: {}", Rc::strong_count(&lis_1))  // 这里是4
}

// 3. 获得 Rc 智能指针的引用计数
println!("lis_1的引用计数为: {}", Rc::strong_count(&lis_1))  // 这里是 3

Cell和RefCell智能指针

Cell<T>一般适合复制语义类型(实现了Copy trait),RefCell<T>一般适合移动语义类型(未实现Copy trait)

Cell<T>无运行时开销,并且永远不会在运行时引发panic错误;RefCell<T>需要在运行时执行借用检查,所以有运行时开销,一旦发现违反借用规则的情况,则会引发线程panic而退出当前线程

内部可变性: 通过 borrow_mut 方法 允许在使用不可变引用的时候获得变量的可变引用 并修改数据: 即Rc<RefCell<T>>

类似于 RC, RefCell只能用于单线程场景, 但是, RefCell使用clone时 会copy一份出来, 而不是像Rc那样引用计数加1

RefCell实现的一个链表

使用 Rc<RefCell<T>>.borrow_mut()可以得到 T 的可变引用

#[derive(Debug)]
enum List{
    Item(Rc<RefCell<i32>>,Rc<List>),
    Null
}
use List::{Item,Null};
// 创建一个变量, 智能指针包裹的RefCell类型的变量,当做 Item 的第一个变量
let a = Rc::new(Item(Rc::new(RefCell::new(5)), Rc::new(Null)));
let b = Item(Rc::new(RefCell::new(9)), Rc::clone(&a));  // 使用 Rc::clone
let c = Item(Rc::new(RefCell::new(1)), a.clone());  // 使用 self.clone()

println!("a: {:?}",a);  // a: Item(RefCell { value: 5 }, Null)
println!("b: {:?}",b);  // b: Item(RefCell { value: 9 }, Item(RefCell { value: 5 }, Null))
println!("c: {:?}",c);  // c: Item(RefCell { value: 1 }, Item(RefCell { value: 5 }, Null))

修改RefCell中的数据

对于RefCell中的数据可以直接修改

let a = RefCell::new(123);  // 创建一个不可变变量
{
    let mut c = a.borrow_mut();  // 可以在不可变的情况下, 获得可变引用
    *c = 2233;  // 直接解引用 修改
}
println!("{:?}", a);


let b = RefCell::new(123);
*b.borrow_mut() = 2233;
println!("Refcell 可以直接修改数据为: {:?}",b);
*(b.borrow_mut()) = 3344;
println!("Refcell 可以直接修改数据为: {:?}",b);


// 3. 修改 Rc<Refcell<T>> 中的 T
let b = Rc::new(RefCell::new(10));
// *((*b).borrow_mut()) *= 20;
*b.borrow_mut() *= 20;
println!("Rc<Refcell<T>> 修改内部数据: {:?}", b);

Box, Rc, RefCell的区别

Rc<T> 允许相同的不可变的数据有多个所有者; Box<T>RefCell<T> 只允许有一个可变的所有者

Box<T> 在编译时执行不可变或可变借用检查; Rc<T>在编译时执行不可变借用检查, RefCell<T>允许在运行时执行不可变或可变检查

也就是 Box 只能有一个所有者可以读写, Rc可以有多个只读所有者, RefCell只在运行时检查(如果有多个所有者也会运行成功, 只不过会发生panic)如果本身不可变的可以通过borrow_mut获得内部的可变性

// mut Box
let mut box_1 = Box::new(123);
let a = box_1.as_mut();  // mut Box 可以通过 as_mut 拿到内部的 &mut i32, 这个函数只限于&mut self, 签名是这样的 fn as_mut(&mut self) -> &mut T
let c = box_1.as_ref();

// Box
let box_1 = Box::new(123);
// let a = box_1.as_mut();  // ! 普通不带mut的 Box 没有实现这个trait
let c = box_1.as_ref();

// mut Rc
let mut rc_1 = Rc::new(123); // 允许相同的不可变的数据有多个所有者, 他clone只增加了一份引用计数, 多份数据使用的其实是一个内存数据, 所以这里不能获得任何可变引用
let a = rc_1.as_ref();


// mut RefCell
let mut ref_c_1 = RefCell::new(1);
let a = ref_c_1.borrow_mut();
// let a = ref_c_1.get_mut();  // 这个也可以调用, 注释是为了不发生panic
// let a = ref_c_1.borrow();  // 同上,

// RefCell
let ref_c_1 = RefCell::new(1);
{
    let mut a = ref_c_1.borrow_mut();
    *a = 123;
}
println!("{:?}", ref_c_1.borrow());
// let a = ref_c_1.get_mut();  // ! 这个方法没有在普通的RefCell中, 虽然没有mut 的RefCell不能通过get_mut拿到内部可变性的数据, 但是可以通过上面的 borrow_mut 拿到
// let a = ref_c_1.borrow();  // 这个也可以调用, 注释是为了不发生panic

// RefCell 的 Clone 实现
let ref_c_2 = ref_c_1.clone();
{
    let mut a = ref_c_2.borrow_mut();
    *a = 234;
}
println!("{:?}", ref_c_1.borrow());  // 123
println!("{:?}", ref_c_2.borrow());  // 234  // 由此可见, Refcell 是完全的Copy出来了一份变量

循环引用问题

由于Rust 中智能指针只有在 strong_count 为0的时候才会被清理, 如果两个变量相互引用, 则出现递归,超过栈深度那么会导致栈溢出, 造成内存泄漏

#[derive(Debug)]
enum List{
    Item(i32, RefCell<Rc<List>>),
    Null
}

// 定义一个获取最尾部元素的方法
impl List{
    fn get_item(&self) -> Option<&RefCell<Rc<List>>> {
        match <