跳转至

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>{

}

在实现块中的约束(早限定)

在impl块中声明的泛型和生命周期参数适用于整个impl块内的所有方法, 这意味着这些参数对impl块内定义的所有方法都是可见的, 并且可以被所有这些方法使用 在最下方的 早限定 介绍中, 可以看到早限定 生命周期的例子

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

impl<T:Copy> People<T>{

}

impl<T:Send> People<T>{

}

impl<T:Say> People<T>{

}

在fn中的约束(晚限定)

在函数签名中声明的泛型和生命周期参数仅对该函数有效, 这意味着这些参数只影响该函数的类型和生命周期要求, 也无法影响同一impl块内的其他函数的泛型和生命周期

impl People{
    fn say<T: Copy>(v: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的生命周期

解糖后的代码(和reborrow 还是有一些区别的, reborrow 不是从原变量出借的而是通过解引用 得到的 另一个引用)

// 解糖后的代码
'a: {
    let mut foo = Foo;
    'b: {
        let loan:&'b Foo = Foo::mutate(&'b mut foo);
        'c: {
            Foo::share(&'c foo);  // 在 'b生命周期期间, 没办法再次从 原变量 出借, 因为其'b 存在一个可变借用
        }
        let x = loan;  // 生命周期被拉长到这里 'b
    }
}
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 self {
            Item(_, item) => Some(item),
            Null => None
        }
    }
}

use List::{Item, Null};
// 创建一个 RefCell<Rc<T>> 的List
let l1 = Rc::new(Item(1, RefCell::new(Rc::new(Null))));

println!("l1_strong_count: {}",Rc::strong_count(&l1));
println!("l1_get_item: {:?}", l1.get_item());

let l2 = Rc::new(Item(2, RefCell::new(Rc::clone(&l1))));  // 这里让 l2 直接使用 l1 的引用
println!("l2_strong_count: {}", Rc::strong_count(&l2));
println!("l2_get_item: {:?}", l2.get_item());

// 修改l1的尾部指向, 这里由于 get_item 返回的是Option<&RefCell<Rc<List>>> 所以可以直接使用 if let Some(v) 进行匹配
if let Some(last) = l1.get_item(){
    *last.borrow_mut() = Rc::clone(&l2)
}
// 再次打印一下 l1 l2 的引用计数
println!("l1_strong_count: {}", Rc::strong_count(&l1));
println!("l2_strong_count: {}", Rc::strong_count(&l2));

// 由于修改了l1的尾部元素让其指向了l2 这里再次打印两者的尾部元素
// println!("l1_get_item: {:?}", l1.get_item());  // # thread 'main' has overflowed its stack 栈溢出了
// println!("l2_get_item: {:?}", l2.get_item());  // 同上

弱引用

RefCell<Weak<List>> 通过Weak::new()来创建一个空的值, 只能通过Rc::downgrade(&self) 向内传递值

弱引用 通过 Rc::downgrade传递 Rc 实例的引用到Weak内, 调用Rc::downgrade会得到Weak<T>的智能指针,同时将 Weak count加1

Weak count 无需为 0, 实例在strong_count为0的时候drop,无视Weak count 的引用计数

Weak<T> 通过 upgrade() 方法来获取弱引用中具体的内容

Weak实现的链表

use std::rc::{Weak, Rc};
use std::cell::RefCell;

#[derive(Debug)]
enum List{
    Item(i32, RefCell<Weak<List>>),
    Null,
}
impl List{
    fn get_item(&self) -> Option<& RefCell<Weak<List>>>{
        match self {
            Item(_,item)=> Some(item),
            Null => None
        }
    }
}

use List::{Item, Null};

let l1 = Rc::new(Item(1, RefCell::new(Weak::new())));  // 使用Rc是为了等下降级为Weak<List>
let l2 = Rc::new(Item(2, RefCell::new(Weak::new())));
println!("l1: {:?}, l2: {:?}", l1, l2);

println!("l1_strong_count: {}, l1_Weak_count: {}",Rc::strong_count(&l1), Rc::weak_count(&l1));
println!("l2_strong_count: {}, l2_Weak_count: {}",Rc::strong_count(&l2), Rc::weak_count(&l2));

// 获取 匹配得到 l2 的尾部, 并传递 l1的引用 到l2尾部
if let Some(item) = l2.get_item(){
    let f = Rc::downgrade(&l1);
    println!("f: {:?}",f);  // 传入了 &l1 的引用, 得到Weak<T>的智能指针, 这里智能指针在下面作为值 传入到 *item.borrow_mut() 中
    println!("f.upgrade(): {:?}",f.upgrade());  // 打印Weak<T>中的具体内容需要 upgrade升级 为Rc智能指针 
    println!("item: {:?}",item);  // RefCell { value: (Weak) }
    println!("item.borrow(): {:?}",item.borrow());  // 打印一下值: 这里 item.borrow_mut() 得到的是 &RefCell<Weak<List>>中的 Weak
    println!("item.borrow_mut(): {:?}",item.borrow_mut());  // 打印一下值: 这里 item.borrow_mut() 得到的是 &RefCell<Weak<List>>中的 Weak
    println!("*item.borrow_mut(): {:?}",*item.borrow_mut());
    *item.borrow_mut() = f;  // 这一步也就是将上面得到的 Weak<T> 智能指针 复制给 item 的 解 可变引用
};
println!("l1_strong_count: {}, l1_Weak_count: {}",Rc::strong_count(&l1), Rc::weak_count(&l1));
println!("l2_strong_count: {}, l2_Weak_count: {}",Rc::strong_count(&l2), Rc::weak_count(&l2));

// 获取 l1 的尾部, 并传递 l2 的引用 到尾部
if let Some(item) = l1.get_item(){
    *item.borrow_mut() = Rc::downgrade(&l2)
};
println!("l1_strong_count: {}, l1_Weak_count: {}",Rc::strong_count(&l1), Rc::weak_count(&l1));
println!("l2_strong_count: {}, l2_Weak_count: {}",Rc::strong_count(&l2), Rc::weak_count(&l2));

println!("l1: {:?}, l2: {:?}", l1, l2);

Weak实现的树形结构

use std::rc::{Weak, Rc};
use std::cell::RefCell;

#[derive(Debug)]
struct Person{
    value:u32,
    parent:RefCell<Weak<Person>>,  // 父节点中 保存 weak弱引用, 如果是 rc 引用,那么则会出现无限递归
    children:RefCell<Vec<Rc<Person>>>
}

// 创建子节点
let leaf = Rc::new(
    Person {
        value: 1,
        parent:RefCell::new(Weak::new()),
        children:RefCell::new(vec![])
    }
);
let p = leaf.parent.borrow().upgrade();

println!("子节点 leaf: {:?}, 子节点的父节点: {:?}",leaf, p);
println!("子节点的引用, strong_count: {}, weak_count: {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf));

// 创建父节点
let branch = Rc::new(
    Person{
        value:2,
        parent:RefCell::new(Weak::new()),
        children:RefCell::new(vec![Rc::clone(&leaf)])
});
println!("父节点 branch: {:?}",branch);
println!("父节点的引用, strong_count: {}, weak_count: {}", Rc::strong_count(&branch), Rc::weak_count(&branch));

// 添加父节点信息到子节点上
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
let p = leaf.parent.borrow().upgrade();

println!("子节点 leaf: {:?}, 子节点的父节点: {:?}",leaf ,p);
println!("子节点的引用, strong_count: {}, weak_count: {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf));
println!("父节点的引用, strong_count: {}, weak_count: {}", Rc::strong_count(&branch), Rc::weak_count(&branch));

多线程

JoinHandle实现的join方法会返回 Result<T>, 当子线程内部发生恐慌时, 该方法会返回Err, 但是通常不会对此类Err进行处理, 当子线程发生恐慌时, 不会影响到其他线程, 恐慌不会在线程间传播

基本使用

直接使用thread::spawn 生成的线程管理对象 JoinHandle<T>,默认没有名称,并且其栈大小默认为2MB

let handle = thread::spawn(|| {
    for i in 0..10{
        println!("thread_i: {}",i);
        thread::sleep(Duration::from_millis(1))
    }
});

for i in 0..5 {
    println!("main_i: {}", i);
    thread::sleep(Duration::from_millis(1))
};
println!("main 执行完毕");

自定义线程

我们也可以使用 thread::Builder 来定制一个有名字和给定大小的线程, 注意: Builder 返回的是 Result<JoinHandle<T>>

stack_size 可以设置 栈大小, 对于堆的大小是无效的

let thread_name = String::from("my_thread");
let size:usize = 1024 * 10;
let builder = thread::Builder::new().name(thread_name).stack_size(size);
let child = builder.spawn(|| {
    println!("my builder thread");
}).unwrap();
child.join().unwrap();

join阻塞主线程

使用 join方法 阻塞主线程, join会等待子线程执行完毕之后主线程才会继续往下运行 (等待子线程执行完毕)

let handle = thread::spawn(|| {
    for i in 0..10{
        println!("thread_i: {}",i);
        thread::sleep(Duration::from_millis(1))
    }
});
handle.join();
println!("main");

主动阻塞和释放

线程内主动阻塞的能力 std::thread::park 提供了阻塞线程的基本能力, std::thread::unpark 可以将阻塞的线程重启, std::thread::park_timeout显示的指定阻塞超时时间

let h1 = thread::spawn(|| {
    println!("准备阻塞");
    thread::park();  // 主动阻塞
    println!("结束阻塞, 开始运行");
});


thread::sleep(std::time::Duration::from_millis(10));
println!("h1线程创建完毕");
let j1 = h1.thread();  // 拿到线程对象
j1.unpark();  // 解除阻塞

h1.join();

继续学习move 关键字

move 移动所有权到线程中( 注意移动到线程中, 主线程中已经没有所有权了 )

使用move是为了防止 多线程内捕获的变量T, 在线程外被提前释放

let mut sum:Vec<u32> = vec![1, 2, 3];
let handle1 = move || {
    println!("通过外部move到线程内部的变量: {:?}", sum)
};
let h1 = thread::spawn(handle1);

// println!("{:?}",sum);  // error: borrow of moved value: `sum`
h1.join();

多线程消息传递

use std::sync::mpsc; let (tx, rx) = mpsc::channel(), 这里使用解构获取 tx: 发送端, rx: 接收端

异步无界Channel: 对应 channel 函数, 发送消息是异步的,并且不会阻塞, 无界,是指在理论上缓冲区是无限的;

通道类似于 单所有权方式, 调用rx send 的时候 会发生 move 操作, 被发送的 变量 后续无法再次使用 (一旦将值发送到另一个线程后,那个线程可能会在我们 后续 再次使用 它之前就将其修改或者丢弃)

发送者或者接收者任一被 drop 时rust就认为通道被关闭了

发送者 tx 的 send 会返回 Result 类型, 如果接收端已经被丢弃了, 将没有发送目标,此时发送会返回错误

接收者 rx 的 recv 方法 当发送通道关闭会返回一个错误

接收者 rx 的 recv 方法 是一个阻塞方法, 不用join即可阻塞所在线程, rx的 try_recv 方法不会阻塞所在线程运行

接收者 rx 本身实现了迭代方法, 迭代器内部实现了 rx的 recv 方法 源码中iterator中next调用self.rx.recv().ok(), 所以下面可以通过 for 循环阻塞 获取消息

!!如果存在多个发送者(共享通道) 注意如果 tx 不被析构, rx在接收消息时会一直阻塞, 死锁 见下面 的例子

channel基本使用

let (tx, rx) = mpsc::channel();  // 异步无界
// 创建一个线程
thread::spawn(move || {
    let val = "hello".to_string();
    let ret = tx.send(val).unwrap();  // 返回 Result 类型
    // println!("{}", val)  // 注意这里所有权已经被转移了
});
// 在主线程中接收
let msg = rx.try_recv();  // try_recv 不会阻塞主线程, 主线程运行到这里, 子线程还没运行所以这里接收的消息为 Err(Empty)
println!("在h1子线程中 try_recv 接收的是: {:?}", msg);
let msg = rx.recv();  // 这里接收者的 recv 方法是一个阻塞方法,所以不需要用join 阻塞, 会等待接收完毕才会向下执行代码
println!("在h1子线程中 recv 接收的是: {:?}",msg);
// 异步无界通道
let (sender, receiver) = channel();
sender.send(1).unwrap();

thread::spawn(move|| {
    for i in 0..10 {
        println!("开始发送 {i}");
        sender.send(i).unwrap();
        println!("发送成功");  // 执行发现, 是一下就执行完了 ^-^
    }
});

for _ in receiver.iter(){
    std::thread::sleep(std::time::Duration::from_secs(1))  // 1秒钟消费1次
}

sync_channel基本使用

// 同步有界通道
let (sender, receiver) = sync_channel(1);  // 通道大小只有1
sender.send(1).unwrap();

thread::spawn(move|| {
    for i in 0..10 {
        println!("开始发送 {i}");
        sender.send(i).unwrap();
        println!("发送成功");  // 由于没有进行读取, 通道是被塞满了, 只有当外部每1秒消费之后, 通道有空, 这里才会发送成功
    }
});

for _ in receiver.iter(){
    std::thread::sleep(std::time::Duration::from_secs(1))  // 1秒钟消费1次
}

单生产者多次发送消息

let (tx, rx) = mpsc::channel::<String>();
thread::spawn( move ||{
    let values = vec!["小明".to_string(), "小花".to_string(), "小亮".to_string()];  // 定义多个数据
    for i in values.into_iter(){  // 循环发送数据
        tx.send(i).unwrap();
        thread::sleep(Duration::from_secs(1)) // 每次发送完毕, 睡眠 1 秒
    }
});
for msg in rx.iter() {
    println!("接收到线程内的消息: {}", msg)
}

多生产者单消费者

多生产者单消费者时, 多个生产者使用 mpsc::Sender::clone 传入消费者 tx 的引用 创建

let (tx, rx) = mpsc::channel::<String>();
let tx1 = mpsc::Sender::clone(&tx);

// 创建两个生产者
thread::spawn(move || {
    let values = vec!["小明".to_string(), "小花".to_string(), "小亮".to_string()];
    for i in values {
        tx.send(format!("tx: {}",i).to_string()).unwrap();
        thread::sleep(Duration::from_secs(1));
    }  // 闭包作用域结束, drop掉 tx, 关闭通道
});
thread::spawn(move || {
    let values = vec!["张三".to_string(), "李四".to_string(), "王二".to_string()];
    for i in values {
        tx1.send(format!("tx1: {}",i).to_string()).unwrap();
        thread::sleep(Duration::from_secs(1));
    }  // 闭包结束 drop 掉tx1, 关闭通道
});

for i in rx{
    println!("取到的数据: {}", i)
}

channel死锁

(共享通道 使用的还是MPSC(多生产者单消费者)队列), 下面的代码中, 在闭包结束的时候仅仅只drop了 tx_clone, 而 rx 会一直阻塞等待消息, 但是有没有了新的消息, 就导致了 tx 不会被drop, 所以造成了死锁

let (tx, rx) = std::sync::mpsc::channel();
for i in 0..5{
    let tx_clone = tx.clone();  // 仅仅是在 for 循环中使用的 clone,而 clone 使用的是 &self, 所以不会造成 tx 在for循环之后被 drop
    thread::spawn(move ||{
        tx_clone.send(i);
    });  // 这里闭包结束, 仅仅 drop 掉了 tx_clone
};
drop(tx);  // 显式 的 drop 掉 tx,在rx之上, 以关闭通道 可以避免死锁(注释掉这一行, 则会发生死锁)

for i in rx {  // 这里发生阻塞
    println!("例子7: channel 死锁, {}", i)
};
println!("如果发生死锁, 那么永远不可能执行到这里");

互斥锁Mutex

任意时刻, 只允许一个线程访问某些数据, 互斥器使用时, 需要先获取到锁, 使用后需要释放锁, 如果获取不到会阻塞线程, 等待别的线程释放锁

Mutex 内部实现了 drop 方法, 锁在离开作用域的时候自动释放, Mutex实例通过 lock 获得锁

Mutex 是一个智能指针, 在调用 lock 时, 返回一个 MutexGuard<T> 的智能指针

一旦获取了锁,就可以将返回值视为一个其内部数据的可变引用了, 所以可以 * 解引用

锁可以提前使用drop()释放

use std::sync::Mutex;
// 创建一个互斥器
let sum = Mutex::new(5);
{
    let mut lock = sum.lock().unwrap();
    *lock = 13;  // 锁在下面离开作用域的时候自动释放 调用了 drop
    drop(lock)  // 提前释放锁
}
println!("Mutex<i32>: {:?}",sum);

Arc智能指针

原子引用计数, 多线程多所有权, 多线程使用的 Arc 智能指针包裹互斥器Mutex(Rc不是线程安全的所以无法使用 如果多线程修改一个数据, 可以使用 Arc 智能指针 进行包裹)

Arc 智能指针是线程安全的

use std::sync::Arc;
let curr = Arc::new(Mutex::new(0));  // Arc 指针 创建一个包含 互斥锁的对象
// 创建10个线程
let mut lis = vec![];
for i in 0..10 {
    let curr_clone = Arc::clone(&curr);  // 为了防止所有权转移, 每次循环 需要 clone Arc指针, 等下传入到线程内部
    let h = thread::spawn(move || {  // 循环创建线程,并将 外部变量 curr_clone 的所有权 传入到内部闭包
        {
            let mut num = curr_clone.lock().unwrap();  // 得到锁, 如果没有得到锁, 那么等待
            *num += 1;
            println!("线程: {}, 对 sum 增加了1, 目前 sum 为 : {}", i, num);  // 下一行作用域结束, 释放锁
        }
        thread::sleep(Duration::from_secs(1))
    });
    lis.push(h)
}
println!("线程创建完毕");
for handle in lis {
    handle.join();  // 依次等待执行
}
println!("线程调度完毕");

读写锁

读线程只允许进行只读访问, 而写线程只能进行独占写操作

读锁和写锁要使用显式作用域块隔离开, 在离开作用域时自动释放, 否则会造成死锁, 因为读锁和写锁不能同时存在

只要线程没有拿到写锁, RwLock<T>就允许任意数量的读线程获得读锁

use std::sync::RwLock;
let lock = RwLock::new(123);
{
    let r1 = lock.read();
    let r2 = lock.read();
}
{
    let w1 = lock.write();
}

原子类型

原子类型本身虽然可以保证原子性, 但它自身不提供在多线程中共享的方法, 所以需要使用 Arc<T>将其跨线程共享

原子类型拥有的操作有:

  • Load(表示读取原子类型内部值)
  • Store(往原子类型内部写入值)
  • CAS(Compare-And_Swap表示比较并交换)
  • Swap(表示原子交换操作)
  • Compare-Exchange(表示比较/交换操作)
  • Fetch-* 表示 fetch_add, fetch_sub, fetch_and 和fetch_or等一系列原子操作

Ordering模块中定义的5种内容不同的内存操作顺序:

  • 排序一致性顺序: SeqCst 最直观、最简单的内存顺序(一般情况下选择该顺序), 必须是先存储store, 再加载load, 多线程环境下, 所有的原子写操作都必须在读操作之前完成, 强行指定了底层多线程的执行顺序
  • 自由顺序: Relaxed 完全不会对线程的顺序进行干涉(尽量不要使用该顺序), 但线程之间会存在竞态条件, 使用这种内存顺序是比较危险的
  • 获取-释放 顺序 Release Acquire 和AcqRel
use std::sync::atomic::{AtomicUsize, Ordering};

// 创建一个Arc包裹原子类型
let spinlock = Arc::new(AtomicUsize::new((1)));
let spinlock_clone = spinlock.clone();

let t = thread::spawn(move || {
    // 修改原子类型的值, 并制指定内存操作顺序
    spinlock_clone.store(0, Ordering::SeqCst);
});
println!("自旋锁开始, 当前状态: {}", spinlock.load(Ordering::SeqCst));

// 死循环(线程内 改变原子类型的值之后, 循环结束)
while spinlock.load(Ordering::SeqCst) != 0 {};

if let Err(panic) = t.join() {
    println!("Error: {:?}", panic);
} else {
    println!("子线程修改了原子类型的值为: {}", spinlock.load(Ordering::SeqCst));
};

Trait 对象

什么是Trait Object呢?指向trait的指针就是Trait Object;假如Bird是一个trait的名称,那么 &dyn BirdBox<dyn Bird> 都是一种Trait Object 一个胖指针;

类似鸭子模型!! 注意 使用 dyn修饰的变量会在编译期包装成为一个 trait Object, 他的大小是不确定的, 因为每个实现了该trait的类型 具体的虚数据和虚表式不确定的

trait本身也是一种类型,但它的类型大小在编译期是无法确定的,所以trait对象必须使用引用 &dyn trait,或智能指针 Box<dyn trait>; trait对象中包含两个指针: data指针和vtable指针

// 标准库的代码: 
struct TraitObject {
    data: *mut (),  // 存放数据
    vtable: *mut (), // 虚表, 存放了函数指针, drop指针, 大小信息, 对齐大小
}

动态分发

在rust 中, 泛型是惰性执行的, 对泛型使用 trait bound 编译器会单态化处理 进行 静态分发, 一旦确定类型, 则后续显然无法再改变, 比如 Clone, 所以这里需要使用 trait 对象

使用 trait 对象时, Rust 必须使用动态分发, 编译器无法知晓可能用于 trait 对象代码的类型,所以也无法知道该调用哪个类型的那个方法, 为此在运行时 使用 trait 对象的指针来知晓需要调用哪个方法, 而不是实例的方法

需要解决的问题

首先看一个错误, 这里 Student 接收一个泛型,且需要实现 Stu trait, 首先 vec 内的第一个参数为 six, rust编译器推导 vec内部的所有元素都同 six 相同类型,但是后续出现了其他类型,这就导致了编译不通过,虽然他们都满足了泛型的条件

// 定义一个类型为Student, 内部有学生列表若干,其中分为six6年级的学生,seven7年级的学生, 他们有一个共同 特征就是 学习
trait Stu{  // 定义 trait
    fn stu(&self);
}

struct Student<T>  // 学生结构体, 需要同时包含 six 和 seven 年级, 这里使用泛型
where T:Stu
{
    lis:Vec<Rc<T>>
}

impl<T> Student<T>  // 学生调动其内的行为
where T:Stu
{
    fn action(&self){
        for i in self.lis.iter(){
            i.stu()
        }
    }
}

struct Six{  // 定义年级结构体
    name:String
}
struct Seven{  // 定义年级结构体
    name:String
}

impl Stu for Six{  // 实现特征
    fn stu(&self) {
        println!("six {}, 正在学习", self.name)
    }
}
impl Stu for Seven{  // 实现特征
    fn stu(&self) {
        println!("seven {}, 正在学习", self.name)
    }
}
// 创建学生实例
let six = Rc::new(Six { name: "小亮".to_string() });
let seven = Rc::new(Seven { name: "小张".to_string()});

// 出错了: expected struct `stu27::Six`, found struct `stu27::Seven`
// let students = Student{
//     lis:vec![six, seven]  // 虽然他们都实现了 trait 中 的 stu 方法,但他们是不同类型, 是无法创建 vec
// };

上方出现错误明显是编译器 认为 vec 内部的元素 T 都是统一类型的, 但是却出现了两种类型,虽然他们都是现实了 Stu trait

使用 dyn 修饰参数为 trait 对象, 替代上方的 泛型加约束的方式

trait Stu{
    fn show(&self) {

    }
}

struct Student{  // 重新定义 学生结构体, 使用 dyn 修饰, 表示这是一个 满足了 trait 对象 (一个胖指针)
    lis: Vec<Rc<dyn Stu>>
}

struct Six{}
struct Seven{}

impl Stu for Six{}
impl Stu for Seven{}

let six = Rc::new(Six {});  // 直接传入结构体到Rc中即可
let seven = Rc::new(Seven {});

let s = Student {
    lis: vec![six, seven]
};

胖指针

struct 一旦变为了 trait object 它的类型就已经被擦除了, 这就需要虚表来查找方法叻, rust 和 c++ 的区别就是, c++ 没有中间状态, c++是直接在 原本的实例上增加一个 vtable指针指向vtable, 而rust会有一个含有data和vtable的对象

use std::mem;
trait Bird {
    fn fly(&self);
}

struct Duck(i32);
struct Swan;

impl Bird for Duck {
    fn fly(&self) { println!("duck duck");}
}

impl Bird for Swan {
    fn fly(&self) { println!("swan swan");}
}

fn print_trait_object(obj: &dyn Bird){  // trait object 类型,obj 是一个胖指针
    // 使用不安全的 rust 获取胖指针内的相关信息
    let p = unsafe { (obj as *const _ as *const usize) };
    let (data, vtable):(usize, usize) = unsafe { mem::transmute(obj) };
    println!("TraitObject [{:?}]:    [data:{}, vtable:{}]", p, data, vtable);

    // 使用as执行强制类型转换,将data从 `usize` 类型转为 `*const i32` 类型
    unsafe {
        let d: *const i32 = data as * const _;
        // 打印出指针 d 指向的内存区间的值
        println!("data in vtable [{}, {}, {}, {}]", *d, *d.offset(1) as i32, *d.offset(2) as i32, *d.offset(3));  // 0下标果然是我们的数据 6789
    }
}
print_trait_object(&Duck(6789));

胖指针大小

trait Object在内存中的表现(可以通过 as 对实例强制类型转换为 trait 对象)

// 获得一个实例
let duck = Duck(6655);
// 获得一个实例的引用
let duck_borrow = &duck;
// 手动获得一个trait对象
let duck__ = duck_borrow as &dyn Bird;

// 分别打印大小(针对对象取指针,得到的是普通指针,它占据 64bit(8字节 1个usize) 的空间;如果我们把这个指针使用as运算符转换为Trait Object,它就成了胖指针,携带了额外的信息)
println!("Size of duck_ {}, Size of duck__ {}", mem::size_of_val(&duck_borrow), mem::size_of_val(&duck__));  // Size of duck_ 8, Size of duck__ 16

对象安全和Sized

trait对象内部维护的是两个表, 方法中 注意是方法中标记有where Self:Sized的方法会被归类到 no_self_vtable也就是说不会被trait对象调用, 虽然是对象安全的但是因为有 Sized约束Self 的方法不会被trait对象调用, 如果在trait上标记 Self:Sized这个trait无法作为安全对象

Self:Sized是为了保证trait默认实现,内部的Self调用都是合法的, 防止函数体内包含Self的默认实现混入虚表(防止Trait对象调用返回带有Self类型且 UNSized 的方法), 因为虚表中的Self无法确定, 必须有Sized才能在虚表中剔除

trait 对象要求对象安全(一个trait如果能实现自己, 就认为它是对象安全的),只有对象安全(object safe) 的 trait 才可以组成 trait对象, trait 对象必须要满足:

  • 返回值类型不为 Self:因为特征对象无法知道返回的Self是什么类型(动态分配),
  • 方法中没有使用任何泛型类型参数
  • 第一个参数必须为 Self 类型或可以解引用为 Self 的类型,
  • self不能出现在除第一个参数之外的地方,包括返回值中(这里self已经有了一个具体的类型了)

总结一句话: 没有额外Self类型参数的非泛型成员方法的 trait

还有一点需要注意的事, trait中声明的方法, 如果出现Self, 则表示是trait本身的类型, 而不是为trait实现的那个类型

// 通过 Self加上Sized 限制胖指针中的函数运行, 表示这个方法是在一个具体的类型中调用, 而不是在虚表中查找, 虽然是对象安全的, 但是 trait对象 无法调用

trait Action{
    fn say(&self){
        println!("say! say! say!");
    }
    fn eat(&self)
        where Self:Sized  // 这里 给eat 添加了 Self:Sized 约束, 那么在trait对象中将无法使用这个方法, 因为 trait对象无法确定大小
    {
        println!("eat! eat! eat!");
    }
}
struct People;
impl Action for People{};

fn action(s:& dyn Action){
    // s.eat();  // Error: this has a `Sized` requirement; 无法编译通过, 因为eat 添加了Self:Sized限定 这就必须保证 trait对象 大小是可知的,然而 trait object 无法可知
    s.say();
};

action(&People);

// 尝试获取一个 trait对象 调用方法
let a = &People as & dyn Action;
a.say();
// a.eat()  Error; this has a `Sized` requirement
trait Action {
    fn eat(&self);
    fn say(&self) where Self:Sized;
}

struct People;

impl Action for People {
    fn eat(&self) {
        println!("eat");
    }
    fn say(&self) where Self: Sized {
        println!("say")
    }
}

let p = People;
let dyn_p = &p as &dyn Action;
// dyn_p.say();  // 禁止调用
dyn_p.eat();

分离不安全的方法

// 添加 Self: Sized 约束, 让 trait对象 不生成对应的方法)
trait Double {
    // fn new() -> Self;  // 使用这个会报错因为在尝试生成 trait object时, 这里 会尝试为trait object 生成 new方法, trait对象中的 Self就是 trait对象类型, 返回了一个 Self,由于trait object 未知大小, 无法分配内存, 所以 编译失败

    fn new() -> Self where Self: Sized;  // 加上 Self: Sized, 编译器编译时, 不会为 trait object 生成这个方法, 虽然返回了 Self未知的大小, 但是编译器由于 Sized限定 根本没有生成这个方法, 剔除了
    fn double(&mut self);
}

impl Double for i32 {
    fn new() -> i32 { 0 }
    fn double(&mut self) { *self *= 2; }
}

let mut i = 1;
let p : &mut Double = &mut i as &mut Double;
p.double();

为什么不允许返回Self

当函数中有Self类型作为参数或者返回类型时, 为什么返回值不允许有Self类型作为参数或者返回类型? (trait对象中的 Self就是 trait对象类型, 因为根本不知道这个trait object到底多大, trait对象无法知道大小,只有运行时才知道, 所以编译失败)

因为 p 是一个 trait object,我们只知道,它是一个“胖指针”,包括了指向具体对象的指针以及指向虚函数表的指针; p指向的具体trait对象,它的类型是什么,已经被擦除, 我们已经不知道了,我们知道的仅仅是,这个类型实现了 Clone trait,其它的就一无所知了;而这个 clone() 方法又要求返回一个与 p 指向的具体类型一致的返回类型;对编译器来说,这是无法完成的任务

// pub trait Clone: Sized {
// fn clone(&self) -> Self;  // 这里返回的是 Self trait 是不知道大小的
// fn clone_from(&mut self, source: &Self) { ... }
// let p: &Clone = ...;
// let o = p.clone();

当函数有泛型参数

x是trait object,通过它调用成员方法,是通过vtable虚函数表来进行查找并调用;现在需要被查找的函数成了泛型函数,而泛型函数在Rust中是编译阶段自动展开的, 在trait对象中没法把这些展开的函数都塞进虚表中

rust 禁止使用trait object来调用泛型函数,泛型函数是从虚函数表中剔除掉了的

trait SomeTrait {
    fn generic_fn<A>(&self, value: A);
}

fn func(x: &dyn SomeTrait) {  // 这个函数无法编译, 因为有泛型则无法创建 trait对象  Error: this trait cannot be made into an object
    x.generic_fn("foo"); // A = &str
    x.generic_fn(1_u8);  // A = u8
}

trait 对象的生命周期

  1. trait对象的生命周期默认是'static;
  2. 如果实现trait的类型包含&'a X&'a mut X,则默认生命周期改为'a;
  3. 如果实现trait的类型包含多个类似T: 'a的从句,则生命周期需要明确指定)
struct P<'a>{p:&'a i32}  // 定义 结构体
trait P_trait{};  // 定义一个 trait
impl<'a> P_trait for P<'a>{};  // 绑定trait
let p = P { p: &123 };
let p_dyn = &p as & dyn P_trait;

需要需要明确指定trait对象生命周期的例子

struct Pstruct_9_1<'a>{p:&'a i32};
trait Ptrait_9_1<'x>{};
impl<'a,'x> Ptrait_9_1<'x> for Pstruct_9_1<'a>{};

// 定义一个返回 trait对象的函数 这里需要明确指明返回的 trait对象的生命周期, 因为 dyn Ptrait_9_1<'b>是一个 trait对象, 需要添加 + 'b 来覆盖默认的 'static 生命周期(传进来的参数p是无法变成'static生命周期的)
fn get_trait_object<'b, 'a: 'b>(p:&'a i32) -> Box<dyn Ptrait_9_1<'b> + 'b>{  // 这里 第一个 'b 表示了struct内所包含引用字段的的生命周期, 第二个 'b 表示了trait的生命周期, 因为trait对象的生命周期默认是'static, 我们返回的内部变量的生命周期是无法变为'static 的 所以需要 + 'b 来覆盖, 且由于是内部生成的返回值, 外部传入的参数肯定要比内部生成的返回值生命要长, 所以 'a: 'b ('a才可以转为'b)
    Box::new(Pstruct_9_1{p})  // 外部接收一个参数, 在这里生成一个 Pstruct_9_1 的实例 并返回一个 trait 对象
}
let p_obj = get_trait_object(&18);
struct People<'a> {
    name: &'a str
}

trait Show<'a> {
    fn show (&self);
}

impl <'a, 'b> Show<'b> for People<'a> {
    fn show(&self) {
        println!("{}", self.name);
    }
}

// 下面函数的解释 来自gpt
// 在 Rust 中, 当你声明一个动态分发的trait对象, 如 Box<dyn Show<'a>>, 其中 'a 指定了trait对象中任何引用的生命周期, 这确保了trait对象中所有引用的生命周期至少为 'a, 然而, 后面跟的 + 'a 部分是对整个 trait 对象生命周期的约束, 它表达了一个不同的概念, 
// + 'a 这个部分是生命周期限定语法的一部分, 它限定了整个 Box<dyn Show<'a>> 对象的生命周期, 这里的 'a 不是指trait方法中引用的生命周期, 而是指这个动态分发的trait对象实例本身的生命周期, 具体来说: 
// dyn Show<'a> 指的是 trait 对象中所包含的引用的生命周期至少为 'a, 这是对trait方法内部使用的引用的约束, 
// + 'a 则是对整个trait对象实例生命周期的约束, 它确保了整个 Box<dyn Show<'a>> 实例的生命周期至少为 'a, 这意味着, 如果某个变量持有了这样的 Box, 那么这个变量的生命周期也至少需要是 'a, 
// 结合在一起, Box<dyn Show<'a> + 'a> 表示一个存储在堆上的、动态类型化的对象, 这个对象实现了 Show 特质, 并且其内部引用的生命周期至少为 'a, 同时这个 Box 实例本身的生命周期也至少为 'a, 这种写法在处理生命周期时提供了额外的清晰度和安全性, 尤其是当trait对象可能被存储并在不同作用域内传递时, 

fn get_dyn<'a>(s:&'a str) -> Box<dyn Show<'a> + 'a> {
    Box::new(People{
        name: s
    })
}

let p_obj = get_dyn("123");

trait对象 无法构造的一些条件

  • 当trait有Self:Sized约束时 (见上方, Self代表调用者的类型, trait对象中的 Self就是 trait对象类型, trait对象无法知道大小,只有运行时才知道)
  • 当函数中有Self类型作为参数或者返回类型时 (见上方的clone)
  • 当函数第一个参数不是self时 (如果一个 trait 中存在静态方法,而又希望通过 trait object 来调用其它的方法如果想调用参照上 静态方法后面加上 Self: Sized 约束,将它从虚函数表中剔除出去)
  • 当函数有泛型参数时 见上方

Any Trait Object

TypeId是一个结构体, 其字段t存储了一串数字, 这就是全局唯一类型标识符

TypeId of函数来获取类型T的全局唯一标识符t(可以判断具体类型)

&dyn Any` 可以使用 `downcast_ref` 可以进行类型转换, 转换any为特定类型 内部源码是`Some(&*(self as *const dyn Any as *const T))
use std::any::{Any, TypeId};
let a = "hello".to_string();
println!("对比String的type_id: {:?}, {:?}", a.type_id(), TypeId::of::<String>());

// Any trait 对象 使用(把引用类型的变量转为一个 trait Object, 可以通过 is 判断类型)
let b = &a as & dyn Any;
println!("通过any 的is函数判断类型: {}", b.is::<String>());

let c = b.downcast_ref::<String>();  // 再转回来, 转换成 Any 是为了有机会获取到他的类型信息,转换回来,则是为了去使用这个值本身
println!("c: {:?}", c);

// 类型当做参数传入fn中,(这里泛型 T 仅仅代表的是一个类型, 而没有具体的值, 需要 method::<type> 传入具体 type)
trait Get{
    fn check_type<T:Any>(&self) -> bool
    where Self:Any
    {
        // 获取类型的名字
        println!("获取类型的名字: {:?}", std::any::type_name::<T>(), );

        // 校验类型
        TypeId::of::<T>() == self.type_id()
    }
}
impl <T:Any> Get for T{};  // 这里的 T:Any 表示 对 后面的 T 进行约束, 表示满足 Get trait 的前置条件是 T实现了Any trait,
let s = "hello".to_string();

s.check_type::<String>();

// 获取类型的名字
println!("type_name 获取类型的名字: {:?}", std::any::type_name::<String>(), );
println!("type_name_of_val 通过值 获取类型的名字: {:?}", std::any::type_name_of_val(&"123".to_string()), );

模式

模式分为可反驳 与 不可反驳的, 能匹配任何传递的可能值的模式是不可反驳的, 对值匹配可能会失败的模式是可被反驳的

  • 不可反驳的模式有:match, 函数, let语句, for循环,因为通过不匹配的值,程序无法进行有意义的工作 必须匹配所有的条件
  • 可反驳的模式: if let, while let 只能接受可反驳的模式,因为他们的定义就是为了有可能失败的条件 可以忽略一些条件

let

// let PATTERN = EXPRESSION
let x = 1;
let (a, b, c) = (1, 2, 3);

"_"

_ 是个 pattern 表示跳过,let 不绑定位置, 因为没有绑定位置, 所以所有权没有转移

let a = "123".to_string();
let _ = a;
println!("{}", a);  // 显然所有权 没有转移

_ 忽略借用规则

let mut s = "hello".to_string();
let s1 = & mut s;
let _ = *s1;    // 忽略借用规则, 解引用并没有让 s 的所有权转移, 注意这里 _ 和 temp 是不同的
&(*s1);  // temp 解引用并没有让 s 的所有权转移
// let a = *s1;  // 禁止 使用具名变量
println!("{}", s);

match

match 字面值匹配 必须匹配所有的情况 可以使用 | 或 操作符, 且必须返回同类型

let num = 1;
match num {
    1 | 3 => println!("match 匹配到了: 1 | 3"),
    2 => println!("match 匹配到了: 2"),
    b => println!("变量匹配上了"),
    _ => println!("match 通配符"),
}

匹配命名变量 (在 match 内是一个独立的作用域)

let x = Some(1);
let y = 2;
match x {
    Some(3) => println!("x: 3"),
    Some(y) => println!("y: {}", y),  // 这里是独立的y, 是一个形参
    _ => println!("other")
};
println!("x: {:?}, y: {:?}", x, y);

通过 .. 进行匹配

let x = 5;
match x {
    0..=5 => println!("使用 0..=5"),
    _ => ()
};
let c = 'c';
match c {
    'a'..='z' => println!("使用 'a'..='z'"),
    _ =>()
}

if let

if let, else if, else if let, else 根据条件匹配

let num = Some(2233);
let i = 13;
if let Some(2) = num{
    println!("if let 匹配到了: {}", 2);
}else if let Some(111) = num{
    println!("else if let 匹配到了: {}", 111)
}else {
    println!("else 什么都没有匹配到")
}

while let

只要模式匹配就会一直执行

let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
    println!("while let : {}", top);
}

for

for 本身就是一个匹配模式

let b = vec![(1,2),(3,4)];
for (c, _) in b.iter(){
    println!("c: {}", c )
}

let b = vec![(1,2,3),(3,4,5)];
for (c, ..) in b.iter(){
    println!("c: {}", c)
}

结构体

// 解构结构体
struct Student{
    name: String,
    age:i32
}
let s1 = Student { name: "小亮".to_string(), age: 13 };
let Student{ name: n, age: a } = &s1;
let Student{ name, age } = &s1;  // 同名的可以简写
println!("解构结构体: name: {}, age:{}",n,a);
println!("解构结构体: name: {}, age:{}",name,age);
assert_eq!(*a,13);

// match 匹配结构体 可以使用  .. 忽略 或者指定具体的值
let n = String::from("小亮");
match s1 {
    Student{name,age:13}=>println!("这个人匹配 年龄 为 13"),
    Student{..}=>println!("这个人名字 年龄 为 小亮"),
    _ => ()
}

// 结构体嵌套结构体 2
struct Point{
    x:i32,
    y:i32
}

let ((a, b), Point{x, y}) = ((1, 2), Point{ x: 3, y: 4 });
println!("a: {}, b: {}, x: {}, y:{}", a, b, x, y);

枚举

// 解构枚举类型
enum Message{
    Quit,
    Move{x:i32,y:i32},
    Write(String),
    ChangeColor(i32, i32, i32)
}
let msg = Message::ChangeColor(255, 255, 255);
match msg {
    Message::Quit => println!("匹配枚举类型的 Quit"),
    Message::Move{x,y}  => println!("匹配枚举类型的 Move: x: {}, y: {}", x, y),
    Message::Write(text) => println!("匹配枚举类型的 Write: {}", text),
    Message::ChangeColor(r, g, b) => println!("匹配枚举类型的 ChangeColor: r: {}, g: {}, b: {}", r, g, b),
}

// 解构嵌套的枚举 1
enum Message_{
    Quit,
    Move{x:i32,y:i32},
    Write(String),
    ChangeColor(Color)
}
enum Color{
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32)
}
let msg = Message_::ChangeColor(Color::Hsv(100, 155, 255));
match msg {
    Message_::ChangeColor(Color::Rgb(r, g, b)) => println!("匹配到了 Rgb格式, r: {}, g:{}, b:{}", r, g, b),
    Message_::ChangeColor(Color::Hsv(h, s, v)) => println!("匹配到了 Hsv格式, h: {}, s:{}, v:{}", h, s, v),
    _=>()
}

部分省略

使用 .. 忽略一部分值, 使用 .. 匹配的时候不能有 歧义(需要有始有终)

let li = (1, 2, 3, 4, 5);
match li {
    (first, .., last) => println!("first: {}, last: {}", first, last)
};
let li = (1, 2);
match li {
    (first, ..) => println!("first: {}", first)
};

match的匹配守卫

let num = Some(11);
match num {
    Some(x) if x < 10 => println!("这个x 小于 10"),
    Some(x) if x > 10 => println!("这个x 大于 10"),
    Some(x) if x == 10 => println!("这个x 等于 10"),
    _ => ()
};
// 匹配守卫 (可以直接使用外部变量, 但是内部变量会沿用自身的)
let num = Some(10);
let y = 10;  // 位置1
let x = 12;  // 位置2
match num {
    Some(x) if x < y => println!("这个x 小于 y"),
    Some(x) if x > y => println!("这个x 大于 y"),
    Some(x) if x == y => println!("这个x 等于 y"),  // 此处的 x 是本作用域的x, y是 位置1 的y
    _ => ()
};

// 匹配守卫的优先级 (3 | 4 | 5) if b 优先判断前面匹配, 然后进入匹配守卫
let x = 3;
let b = false;
match x {
    3 | 4 | 5  if b => println!("匹配守卫的优先级"),
    _ => ()
}

@ 运算符

允许我们在创建一个存放值得同时, 测试这个变量是否匹配模式

enum _Message{
    Idx { id: i32 }
}
let msg = _Message::Idx { id: 2233 };

match msg {  // 普通的匹配模式, 匹配成功后结构体内的值进行过滤, 是不能的
    _Message::Idx {id:i} => println!("普通的过滤方式无法限制 id 的值 i: {}", i),
}

match msg {  // 使用 @ 运算符 可以对结构体/枚举内的值过滤并赋值
    _Message::Idx { id: id_value @ 3..=18 } => println!("使用 @ 运算符,匹配到了一些值 id: {}", id_value),
    _ => ()
}

// 好像 if 也可以哈 
match msg {  // 普通的匹配模式, 匹配成功后结构体内的值进行过滤, 是不能的
    _Message::Idx {id:i} if i < 9999 => println!("好像 if 也可以过滤 i: {}", i),
    _ => {}
}

ref

ref在模式匹配中, 可以对 & 进行匹配, 而不是获得其所有权

为了防止变量匹配之前进行了一次 隐式let 模式匹配导致出借所有权

let a = &Some("123".to_string());
match *a {  // 如果匹配 a的解引用, 为了防止内部的字符串所有权不被转移, 需要使用 ref 进行匹配
    Some(ref s) => { println!("{}", s);}
    // Some(& s) => { println!("{}", s);}  Error
    None => {}
}
println!("{:?}", a);

新版Rust 不需要解引用 *a 再进行匹配了, 可以直接匹配 a, rust编译器会自动添加 ref 或者 ref mut

match a {
    Some(s) =>{},
    None =>{},
}
println!("{:?}", a);

不安全的Rust

unsafe 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果在不安全代码中使用引用,它仍会被检查,unsafe 关键字只是提供了那五个不会被编译器检查内存安全的功能,你仍然能在不安全块中获得某种程度的安全

  • 解引用裸指针
  • 调用不安全的函数或方法
  • 访问或修改可变静态变量
  • 实现不安全 trait
  • 访问 union 的字段

解引用裸指针

解引用裸指针, 分为可变和不可变: *mut T, *const T, 这里 * 是类型的一部分, 并不是解引用, 意为 可变指针/指针常量,

  • 裸指针允许忽略借用规则, 可以同时拥有多个可变和不可变的指针,或者多个指向相同位置的可变指针
  • 不保证指向的内存是有效的(没有生命周期概念, 编译器不会对其进行借用检查)
  • 允许为空
  • 不能实现任何自动清理的功能(*const T不会进行drop检查, 但是这一点可以使用PhantomData<T>幽灵数据来配合对T类型进行drop检查 )

基本使用

可以在安全的代码中创建 可变/不可变 裸指针, 只是不能使用和解引用, 如果想使用和解引用,需要在unsafe的block中

let mut num = 3;
let r1 = num as *const i32;
let r2 = num as *mut i32;
// println!("r1; {:?}", *r1);  // 安全的代码中禁止使用不安全的变量

unsafe {
    // println!("r1; {:?}", *r1);
    // println!("r2; {:?}", *r2);
    println!("r1; {:?}", r1);
    println!("r2; {:?}", r2);
}

允许为空 下面可能就是一个空的内存地址

let add = 0x12345usize;
let _r = add as *const i32;

调用不安全的函数或者方法

unsafe fn dangerous(){
    println!("dangerous fucntion")
};
// dangerous();  // 禁止调用

unsafe {  // 在unsafe的block中才可以调用
    dangerous()
}

创建不安全的代码抽象

fn function_1(){
    let mut num = 3;
    let r1 = num as *const i32;
    let r2 = num as *mut i32;

    unsafe {
        // println!("r1; {:?}", *r1);
        // println!("r2; {:?}", *r2);
    }
}
function_1();

调用 c 语言的函数, 属于不安全的代码

extern "C" {
    fn abs(input:i32) -> i32;
}
unsafe {
    println!("abs(-3) = {}", abs(-3));
}

不安全的可变静态变量

静态变量和常量的区别: 静态变量有固定的内存地址,使用这个值总是访问相同的地址, 常量则允许任何被用到的时候复制其数据

静态变量可以是可变的, 虽然可变后是不安全的

可变的静态变量 是无法在安全的代码中 读取和修改的

静态变量和程序代码一起被存储于静态存储区中

static NUM_STR:&str = "2233";
static mut COUNTER:u32 = 0;

println!("静态变量, NUM_STR: {}",NUM_STR);  // 读取全局的静态变量

// COUNTER += 1;  // 禁止在安全的代码中 修改可变静态变量
// println!("COUNTER: {}", COUNTER);  // 安全的代码中, 禁止读取 可变静态变量
unsafe {
    COUNTER += 1;
    println!("COUNTER: {}", COUNTER)
}

不安全的 trait

当至少有一个方法包含编译器不能验证的 变量 时, 该 trait 就是不安全的

在 trait 之前增加 unsafe 声明这是一个不安全的trait, 同时 trait 的实现也必须用 unsafe 标记, 比如Send 就需要unsafe标记

unsafe trait Say{
    fn say(&self);
}
struct Student;

unsafe impl Say for Student{
    fn say(&self){
        println!("say, say, hello")
    }
}

let s1 = Student{};
s1.say()

PhantomData幽灵数据

struct Cache<T, U, R>
    where T: Fn(R) -> U,  // R泛型只出现在了这里(约束条件里), 没有出现在参数里, 所以编译期提示未使用的泛型会禁止编译,
          U:Copy
{
    callback: T,
    value: Option<U>,
    _tmp: PhantomData<R>  // 这里使用一个幽灵数据即可(好像可以直接用 Option<R> 这个类型也行? 然后字段用None? )
}

impl<T, U, R> Cache<T, U, R>
    where T: Fn(R) -> U,
          U:Copy
{
    fn value(&mut self, args: R) -> U {
        if let Some(v) = self.value {
            v
        } else {
            let v = (self.callback)(args);
            self.value = Some(v);
            v
        }
    }
}

let mut cache = Cache {
    callback: |x| x,
    value: None,
    _tmp: Default::default(),
};

let v = cache.value(1);
let v = cache.value(2);
println!("{v}")

关联类型

关联类型在 trait 定义中 指定占位符类型

关联类型是一个 将类型占位符与trait 相关联的方式

trait 的实现者 会针对特定的实现,在这个类型的指定位置指定相应的具体类型(可以定义一个使用多种类型的 trait, 直到实现此 trait 时都无需知道这些类型具体是什么)

标准库中的Iterator

trait Iterator{
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
};

解决的问题

如果不使用关联类型, 而是使用泛型那么会出现一些问题, 出现无法推断类型的情况(因为泛型, 在编译期会展开)

trait Iterator<T>{
    fn next(& mut self) -> Option<T>;
}

// 定义迭代器
struct List{
    value:i32
}
// 实现迭代器特征 String
impl Iterator<String> for List{
    fn next(&mut self) -> Option<String>{
        println!("调用了 String 的迭代器");
        if self.value <5 {
            self.value += 1;
            Some(String::from("哈哈"))
        }else { None }
    }
}
// 实现迭代器特征 i32 类型
impl Iterator<i32> for List{
    fn next(&mut self) -> Option<i32>{
        println!("调用了 i32 的迭代器");
        if self.value <5 {
            self.value += 1;
            Some(self.value)
        }else { None }
    }
}

// 下面 使用 next 已经无法推断类型了
let mut l1 = List { value: 1 };
// l1.next();  // 无法推导类型Error: cannot infer type for type parameter `T` declared on the trait `Iterator`

// 使用完全限定语法可以调用上面无法推断类型的迭代器 TODO 但是这是超出预期的, 与我们的设计想法不符
<List as Iterator<i32>>::next(&mut l1);
<List as Iterator<String>>::next(&mut l1);

下面这个 试用了 关联类型 好像也出错了, 关联类型到底有啥用

struct MyIter{
    value: i32
};

trait MyIterTrait{
    type Item;
    fn next_1(&mut self) -> Option<Self::Item>;
};

impl MyIterTrait for MyIter {
    type Item = i32;
    fn next_1(&mut self) -> Option<Self::Item> {
        println!("i32 next_1");
        Some(1)
    }
}
impl MyIterTrait for MyIter {
    type Item = String;
    fn next_1(&mut self) -> Option<Self::Item> {
        println!("String next_1");
        Some("2233".to_string())
    }
}

let mut mi = MyIter { value: 1 };
mi.next_1();
mi.next_1();

ps: 好像用泛型也没什么用啊

就是起了个别名嘛

struct MyIter<T>{
    value: T
};

trait MyIterTrait1{
    type Item;
    fn next_1(&mut self) -> Option<Self::Item>;
};

trait MyIterTrait2<T>{
    fn next_2(&mut self) -> Option<T>;
};


impl<T:Copy> MyIterTrait1 for MyIter<T> {
    type Item = T;
    fn next_1(&mut self) -> Option<Self::Item> {
        Some(self.value)
    }
}

impl<T:Copy> MyIterTrait2<T> for MyIter<T> {
    fn next_2(&mut self) -> Option<T> {
        Some(self.value)
    }
}

let mut mi_1 = MyIter { value: 1u32 };
println!("{:?}", mi_1.next_1());
println!("{:?}", mi_1.next_2());

let mut mi_2 = MyIter { value: 1u64 };
mi_2.next_1();
mi_2.next_2();

关联类型的约束

在使用trait 约束泛型的时候, 使用trait内的 关联类型 进行再次约束

trait MyTrait{
    type Flag;
}

fn my_utils<T>(f:T) -> ()
    where T: MyTrait<Flag=i32>  // 对于实现了 Mytrait 特征的参数, 其内的 Flag 关联类型 也必须为 i32 类型
{

}

struct MyStruct1;
struct MyStruct2;

impl MyTrait for MyStruct1{
    type Flag = i32;
};
impl MyTrait for MyStruct2{
    type Flag = f32;
};

my_utils(MyStruct1);
// my_utils(MyStruct2);  // Error: expected `i32`, found `f32`

默认泛型类型参数和运算符重载

使用泛型类型参数时, 可以为泛型指定一个默认的具体类型

运算符重载是指在特定情况下自定义运算符行为的操作

Rust不允许创建自定义的运算符或者重载运算符, 不过对于std::ops中列出的运算符和相应的 trait, 我们可以使用运算符相关 trait 来重载

基本使用

use std::ops::Add;

#[derive(Debug)]
struct Point{
    x:i32,
    y:i32
}

// 重载 Point 的 + 操作
impl Add for Point{
    type Output = Point;
    fn add(self, other:Point) -> Point{
        Point{
            x:self.x + other.x,
            y:self.y + other.y
        }
    }
}
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 3, y: 5 };
let p3 = p1 + p2;
println!("p1 + p2 = {:?}", p3);

默认泛型的类型RHS

尖括号里面为默认类型参数, RHS 是一个 泛型类型参数 right hand side, 这里大写的RHS 是用来指定下方小写rhs的类型的, 这里默认使用了 Self 所以只能和自己本身的类型相加

RHS和Output,分别代表加法操作符右侧的类型和返回值的类型

pub trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}
use std::ops::Add;
struct Ms1(u32);
struct Ms2(u32);
impl Add<Ms2> for Ms1{  // 这里 Ms2 就是准备与之相加的结构体
    type Output = Ms1;  // 定义 trait中的关联类型
    fn add(self,other:Ms2) -> Self::Output{
        Ms1(self.0 + other.0)
    }
}
let m1 = Ms1(1);
let m2 = Ms2(2);
let m3 = m1 + m2;
// println!("{}",m2.0);  // error: value borrowed here after move
trait Stu<T=i32>{  // 写法1 (设置默认泛型类型)
    fn say(&self, value:T)
// trait Stu{  // 写法2 为结构体实现trait的时候 才进行约束
//     fn say<T>(&self, value:T)
    where T:Display
    {
        println!("{}", value);
    }
}

struct Student_{};

impl Stu<String> for Student_{};  // 这里在实现 trait 时 指定类型
let s = Student_{};
s.say("123".to_string())

完全限定语法

Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait,甚至直接在类型上实现开始已经有的同名方法也是可能的

使用 <Type as trait>::function(), 调用 特定 Type 特定 trait 的方法,<Type as Trait>::function(receiver_if_method, next_arg, ...);

有 self 参数的 同名 方法

如果trait 和结构体本身的 方法重复, 且有self 参数,那么 在调用方法时 可以自动匹配到对应的方法

// 定义两个trait
trait A{
    fn print(&self);
};
trait B{
    fn print(&self);
};

// 结构体
struct MyType;

// 特征实现
impl A for MyType{
    fn print(&self){
        println!("this is A trait")
    }
}
impl B for MyType{
    fn print(&self){
        println!("this is B trait")
    }
}

// 本身方法
impl MyType{
    fn print(&self){
        println!("MyType")
    }
}

// 创建实例
let my_type = MyType;

my_type.print(); // 等价于下面的方法
MyType::print(&my_type);

// 调用 MyType 实现了 特征 A/B 的 方法
A::print(&my_type);
B::print(&my_type);

调用没有 self 参数的方法

trait Animal {
    fn get_name() -> String;
}

struct Dog;
impl Dog{
    fn get_name()->String {
        "Dog".to_string()
    }
}

impl Animal for Dog{
    fn get_name() -> String {
        "Animal trait Dog".to_string()
    }
}

// 调用 Dog 本身的方法
println!("Dog 本身方法: {}", Dog::get_name());

// 调用 Animal 特征的方法
// println!("Animal trait 的方法: {}", Animal::get_name());  // error: required by `stu34::Animal::get_name`

// 使用完全限定语法调用 指定 trait 的方法
let dog_name = <Dog as Animal>::get_name();
println!("使用完全限定语法: {}", dog_name);

父trait

类似于为 trait 增加 trait bound, 他和 为 trait 增加 trait约束 是有区别的

类似 "继承", 有时候我们需要某个 trait 使用另一个 trait 的功能, 可以使用 trait trait1:trait2 + trait3 {} 的方法定义 trait

impl trait1:trait2 + trait3 for T {}, 实现了 trait1的类型, 必须也实现 trait2 和trait3, 否则无法编译失败

use std::fmt;  // 引入trait

// 定义一个特征, 这个特征也拥有一些继承过来的特征
trait Output: fmt::Display{  // 这里 "继承" fmt::Display
    fn out_print(&self){
        let output = self.to_string();
        println!("out_print: {}", output)
    }
}

// 定义一个结构体
#[derive(Debug)]
struct Point{
    x:i32,
    y:i32
};

// 实现 Output 特征
impl Output for Point{};
// 实现 fmt::Display 特征, 因为这是 Output 特征继承的,如果实现了Output特征, 那么也必须实现继承过来的特征
impl fmt::Display for Point{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        println!("实现了 Display");  // 这里在我本机上明没有打印 奇怪
        write!(f, "{}, {}",self.x, self.y)
    }
}
let p1 = Point{x:1, y:2};
println!("{:?}",p1)

孤立规则newtype

孤立规则: 只要 trait 或者类型 对于当前 crate 是本地的话,就可以再此类型上实现该trait

如果要实现某个trait,那么该trait和要实现该trait的那个类型至少有一个要在当前crate中定义

newtype 模式在外部类型上实现外部的trait, 比如一个结构体

绕开孤立规则的方法就是使用 newtype 模式

使用 Box 脱离孤立规则: Rust 内部使用了一个叫#[fundamental]的属性标识, 使Box 脱离孤儿规则, 源码中可得知; 除了Box<T>,还有Fn、FnMut、FnOnce、Sized

需要解决的问题

下面如果使用被注释的那行,会报错, is not defined in the current crate, 很明显这里 Option 并不是在本地 crate 实现的 struct, 而是外部标准库引用过来的违反孤立规则在本地不能给它实现特征

而再下面一行的 MyData 是本地的, 所以使用 trait也不会报错

use std::ops::Add;
struct MyData(i32);
// impl Add<i32> for Option<MyData>{  // 不可以, 因为 option 不是本地的而且 Add也不是本地的
// impl Add<i32> for Box<MyData>{  // 可以, 使用 box 可以绕开
impl Add<i32> for MyData{
    type Output =i32;
    fn add (self,other:i32)->Self::Output{
        (self.0) + other
    }
}

实现一个简单的newtype

#[derive(Debug)]
struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{
        write!(f, "({})",self.0.join(", "))
    }
}

Box::new(123);
let w = Wrapper(vec![String::from("小红"), String::from("小明")]);  // 生成一个 newtype(比如这里是一个类似 vec 的类型)
println!("{:?}", w);

动态大小类型

Sized用来标识有尺寸大小类型

Unsize标识的是动态大小类型, 在编译期无法确定其大小, 目前Rust中的动态类型有trait和[T], 只可以通过胖指针来操作Unsize类型,比如&[T]&Trait

?Sized标识的类型包含了SizedUnsize所标识的两种类型

基本介绍

动态大小类型: DST, 这些类型只有在运行的时候才知道大小的类型, 比如 &str 类型

必须将动态大小类型的值, 置于某种指针之后: 如Box<str>, Rc<str>

另一个动态大小类型是 trait, 每一个 trait 都是一个可以通过 trait 名称来引用的 动态大小类型, 为了将 trait 用于 trait 对象, 必须将他们放到指针之后, 比如 &TraitBox<Trait> Rc<Trait>

Rust提供了两种主要的 DST: trait 对象和 slice, slice的&[T], 这个是&[T] 的内存表示, 不是[T]的内存表示,[T]没有值

&str

&str 是Unsize, 是一个胖指针,保存了两个值: str的地址和长度. &str的大小在编译时就可以知道,大小是 2*usize

let s1 :&str = "hi";

Sized trait

为了除了 DST, Rust 用Sized trait 来决定一个类型的大小是否是在编译时可知

这个 trait 自动为编译器在编译时就知道大小的类型实现

fn generic<T>(t:T){};  // T 为编译时就自动推导大小和 类型, 和下面等价
fn generic_<T:Sized>(t:T){};  // 和上面等价, T 要求实现 Sized 的特征

Sized放宽

?Sized 包括了所有的动态大小类型和所有可确定大小的类型

对于泛型约束使用 ?Sized, 有可能传入的参数我们也不知道大小, 所以这里需要忽略 T 的大小, 可能是 Sized, 也可能不是 Sized

fn _generic<T: ?Sized>(t: &T){};

高阶函数和闭包

函数指针允许我们 使 函数 作为另一个函数的参数

函数的类型是 fn, fn被称为函数指针, 指定参数为函数指针的语法类似于闭包, 他是一个类型

fn函数指针实现了闭包的三个trait: Fn, FnMut, FnOnce

基本使用

fn add_one(x:i32) -> i32 {
    x + 1
}
fn utils(func:fn(i32) -> i32, val:i32) -> i32 {
    func(val)
}

let num = utils(add_one, 3);
println!("num: {}",num);

使用泛型

实现一个泛型修饰函数, 要求传入的第一个参数为 实现了 Fn trait 的类型

fn wrapper_func<T>(f:T, val:i32) -> i32
where T: Fn(i32) -> i32
{
    f(val)
}

fn func(v:i32) -> i32{
    v + 1
}

let a = wrapper_func(|x| x * 2, 3);  // 使用闭包
let b = wrapper_func(func, 3);  // 使用函数指针

函数/闭包 作为返回值

返回函数指针

fn return_func_1()-> fn(i32) -> i32{
    |x| x + 1
}
let func_1 = return_func_1();
let num_1 = func_1(5);
println!("return_func_1: {}", num_1);

返回 Fn trait

返回满足了 Fn trait 特征的对象, 使用 dyn 修饰表示这里是满足了 Fn trait 的对象

fn return_f() -> impl Fn(i32) -> i32{
    | x:i32 | x
}
fn return_func_2() -> Box<dyn Fn(i32) -> i32>{
    Box::new( |x| x * x)
}
let func_2 = return_func_2();
let num_2 = func_2(3);
println!("return_func_2: {}", num_2)

From/Into

From 和 Into 两个 trait 是内在地联系着的,事实上这是它们的实现的重要 部分:如果能把类型 A 转换成类型 B,那么很容易相信我们也能把类型 B 转换成类型 A

你的类型实现了From ,那么同时你也就免费获得了 Into

使用 Into trait 通常要求指明要转换到的类型,因为编译器大多数时候不能推断转为哪一个类型,因为一个类型可能会有很多 Into

TryFrom 和 TryInto 是 类型转换的通用 trait;不同于 From / Into 的是, TryFrom 和 TryInto trait 用于易出错的转换,也正因如此,其返回值是 Result 型

关于Into有一条默认的规则: 如果类型U实现了From<T>,则T类型实例调用into方法就可以转换为类型U, 调用时会把 T当做参数传入U的from方法中

#[derive(Debug)]
struct Number{
    val:i32
}

// 为结构体实现 From trait
impl From<i32> for Number{
    fn from(val:i32) -> Self{
        println!("调用了 from");
        Number{val}
    }
}

let i = Number::from(123);
println!("i: {:?}",i);

// println!("i: {:?}",i.into());  // error: cannot infer type for type parameter `T` declared on the trait `Into`
// let i_into = i.into();  error: // consider giving `i_into` a type
let i_into_ok:Result<Number,_> = 2123123.try_into();  // 注意!: 使用 try_into 的时候需要给与返回值一个类型,因为编译器无法推导
println!("i_into:Result<Number,_>: {:?}",i_into_ok);

let i_ = Number::from(2233);
let i_into:Number = 123.into();  // 注意!: 使用 into 的时候需要给与返回值一个类型,因为编译器无法推导转为哪一个类型
println!("i_into:Number,_>: {:?}",i_into);
#[derive(Debug)]
struct Complex {
    re: i32,
    im: i32
}
impl From<i32> for Complex{
    fn from(re: i32) -> Self {
        Complex{
            re,
            im:0
        }
    }
}

println!("Hello, world!");
let b: Complex = 1.into();  // 需要手动注明类型, 因为编译器无法自动推导
println!("Complex: {:?}", b);

使用Into trait 约束泛型

用 into()方法 的时候,可以使用关联类型约束, 而且 会直接把调用者传入 from 中的形参中, 前提是已经实现了相应的 From/Into

fn utils<T>(s:T)
    where T:Into<Complex>
{
    println!("s.into {:?}",s.into())  // s作为调用者, 被传入到了 from中的参数中了
}
utils(123);

网络编程

单线程处理网络请求

use std::net::{TcpListener, TcpStream, SocketAddr, IpAddr};
use std::io::Read;
use std::time;
use std::thread;

// listener.incoming 相当于loop循环中的 listener.accept()

// 由于 str 实现了 ToSocketAddrs trait 所以可以直接传入并绑定 ip:port
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();


// 接收消息
for msg in listener.incoming(){  // 内部实现了 迭代器 特征可以直接迭代
    match msg {
        Ok(stream) => client_handle(stream),
        _ => {}
    }
};

// 对请求的内容进行处理
fn client_handle(mut stream:TcpStream){
    // 读取 1
    let mut value:[u8;512] = [0;512];  // 注意这里保存的字节, 需要在下面 转一下类型
    stream.read(& mut value).unwrap();  // 使 流读取的内容保存到 value 中
    println!("收到了一些内容: {}", String::from_utf8_lossy(&value));  // 使用 from_utf8_lossy 方法把字节转为 String


    // 返回
    let response = "HTTP/1.1 200 OK \r\n\r\n";
    println!("返回的消息为: {:?}", response.as_bytes());
    stream.write(response.as_bytes());  // 把response 写入到流中,返回
    stream.flush();  // 处理完毕, 刷新流

    // 手动睡眠 10秒, 模拟延时操作
    thread::sleep(time::Duration::from_secs(1));
}

Ok(())

多线程处理网络请求

use std::net::{TcpListener, TcpStream, SocketAddr, IpAddr};
use std::io::Read;
use std::time;
use std::thread;

// 由于 str 实现了 ToSocketAddrs trait 所以可以直接传入并绑定 ip:port
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();

// 接收消息(这里使用多线程处理)
for msg in listener.incoming(){  // 内部实现了 迭代器 特征可以直接迭代
    match msg {
        Ok(stream) => {thread::spawn(move ||client_handle(stream));},
        _ => {}
    }
};

// 对请求的内容进行处理
fn client_handle(mut stream:TcpStream){
    // 读取
    let mut value:[u8;512] = [0;512];  // 注意这里保存的字节, 需要在下面 转一下类型
    stream.read(& mut value).unwrap();  // 使 流读取的内容保存到 value 中
    println!("收到了一些内容: {}", String::from_utf8_lossy(&value));  // 使用 from_utf8_lossy 方法把字节转为 String


    // 返回
    let response = "HTTP/1.1 200 OK \r\n\r\n <!DOCTYPE html>123123";
    stream.write(response.as_bytes());  // 把response 写入到流中,返回
    stream.flush();  // 处理完毕, 刷新流

    // 手动睡眠 10秒, 模拟延时操作
    thread::sleep(time::Duration::from_secs(3));
}

println!("结束了");
Ok(())

线程池处理网络请求

use std::net::{TcpListener, TcpStream, SocketAddr, IpAddr};
use std::io::Read;
use std::time;
use std::thread;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};

// 任务结构体
#[derive(Debug)]
struct Worker{
    id:usize,
    thread:Option<thread::JoinHandle<()>>
}
// 任务方法: 返回一个任务方法
impl Worker{
    // 1.
    // fn new(id:usize) -> Self {
        // let t = thread::spawn(move || {});
        // Worker{id,thread:t}
    // }

    // fn new(id:usize, receiver:mpsc::Receiver<Job>) ->Self {
    fn new(id:usize, receiver:Arc<Mutex<mpsc::Receiver<Message>>>) ->Self {  // receiver是一个 一个线程安全的的接收端
        let thread = thread::spawn(move || {  // 创建一个子线程, 子线程在后台loop循环接收任务
            loop{  // 无限循环 接收端 阻塞 接收任务
                let message = receiver.lock().unwrap().recv().unwrap();  // 如果收到通道的内容
                match message {  // 判断通道中内容的类型,是执行新的任务还是停止当前子线程
                    Message::Run(job)=>{
                        println!("收到任务: {:?}", id);
                        job()
                    }
                    Message::Stop => {  // 如果收到Stop 则子线程 立刻停止循环, 下面即执行完毕
                        println!("结束任务: {}", id);
                        break
                    }
                }
            }
        });
        Worker{id, thread:Some(thread)}  // 返回这个任务到外部主线程
    }
}

enum Message{  // 发送给channel中的消息: Run(新增一个任务), Stop(停止该线程)
    Run(Job),
    Stop
}

// 一个 类型别名, 当做 线程池接收的 函数 的约束
type Job = Box<dyn Send + FnOnce() + 'static>;


// 定义线程池
struct ThreadPool{
    // threads:Vec<thread::JoinHandle<()>>
    workers:Vec<Worker>,
    sender:mpsc::Sender<Message>
}

impl ThreadPool{
    fn new(size:usize) -> Self {
        assert!(size > 0);
        // let mut threads = Vec::with_capacity(size);
        // 线程需要传入闭包

        // ThreadPool{threads}

        let mut workers = Vec::with_capacity(size);   // 任务容器
        let (sender, receiver) = mpsc::channel::<Message>();  // 发送端, 接收端
        let receiver = Arc::new(Mutex::new(receiver));  // 线程安全的接收端

        for id in 0..size{  // 循环 根据 size 创建 子线程, 子线程内接受一个 receiver 接收端, 在线程内循环阻塞接收任务
            let worker = Worker::new(id, Arc::clone(&receiver));
            println!("创建了子线程: {:?}", worker);
            workers.push(worker);
        };
        ThreadPool{  // 返回创建的线程池
            workers,
            sender
        }
    }

    fn execute<F>(&self, f:F)  // 提交任务
    where F:FnOnce() -> () + Send + 'static
    {
        let job = Box::new(f);  // 使用 Box 封装为一个智能指针
        self.sender.send(Message::Run(job)).unwrap();
    }
}

// 线程池关闭处理
impl Drop for ThreadPool{
    fn drop(&mut self){

        // 给所有线程发送停止消息(每个线程收到消息后会 break,停止loop接收后续消息了)
        for _ in 0..self.workers.len(){
            self.sender.send(Message::Stop);
        }

        // 循环拿出所有的worker, 等待所有的 worker内部 的线程结束
        for worker in &mut self.workers{
            println!("{:?}", worker);
            // worker.thread.join().unwrap();  // Error: cannot move out of `*s` which is behind a mutable reference; 因为worker.thread在 可变引用 之后, 调用这个方法会自动解引用, 在上方使用了 &mut worker可变引用, 可变引用未实现Copy trait 解引用会转移所有权

            if let Some(thread) = worker.thread.take(){  // 使用 take 拿到Option中的内容,内部补为None, 且不会转移 worker的所有权
                thread.join();  // 等待子线程结束
            }
        }
        println!("线程池关闭")
    }
}


println!("Server Start");
// 由于 &str 实现了 ToSocketAddrs trait 所以可以直接传入并绑定 ip:port
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();

let thread_pool = ThreadPool::new(2);

// 接收连接(这里使用多线程处理), 使用take, 表示只接收10次连接, 10次连接之后,就不再阻塞接收消息, 直接结束程序
for msg in listener.incoming().take(10){  // 内部实现了 迭代器 特征可以直接迭代
    match msg {
        Ok(stream) => {
            thread_pool.execute(|| client_handle(stream));  // 接收到连接之后 提交任务到线程池中
        },
        _ => {}
    }
};

// 对请求的内容进行处理
fn client_handle(mut stream:TcpStream){
    // 读取
    let mut value:[u8;512] = [0;512];  // 注意这里保存的字节, 需要在下面 转一下类型
    stream.read(& mut value).unwrap();  // 使 流读取的内容保存到 value 中
    // println!("收到了一些内容: {}", String::from_utf8_lossy(&value));  // 使用 from_utf8_lossy 方法把字节转为 String

    // 返回
    let response = "HTTP/1.1 200 OK \r\n\r\n <!DOCTYPE html>123123";
    stream.write(response.as_bytes());  // 把response 写入到流中,返回
    stream.flush();  // 处理完毕, 刷新流

    // 手动睡眠 10秒, 模拟延时操作
    thread::sleep(time::Duration::from_secs(10));
}


println!("结束了");
Ok(())

TcpClient

use std::net::{TcpStream};
use std::time;
use std::io::{Result,self, BufReader};

// 创建连接
let mut stream = TcpStream::connect("192.168.2.34:8080").unwrap();

// 发送10次消息
for i in 0..10{
    // 从标准流让用户输入
    // let mut msg = String::new();
    // io::stdin().read_line(&mut msg);
    let mut msg = format!("发送: {}", i).to_string();

    // 从当前的流中读取消息(使用 BufReader 提高读取速度)
    let mut stream = BufReader::new(&stream);

    let mut buffer:Vec<u8> = Vec::new();
    stream.read_until(b'\n', &mut buffer).expect("读取失败");
    println!("读取的内容: {}", String::from_utf8_lossy(&buffer));

    // 发送消息 直接使用 BufReader包裹的stream 写入消息 get_mut() 返回的就是内部的stream
    stream.get_mut().write(msg.as_bytes()).unwrap();
    stream.get_mut().write("\n".as_bytes());
    stream.get_mut().flush();

}
Ok(())

UDPServer

use std::net::UdpSocket;

// 监听端口
let socket = UdpSocket::bind("192.168.2.34:8080")?;

// 循环接收消息
loop {
    // 创建缓冲区
    let mut buf = [0u8; 512];
    // 读取消息, 得到一个读取的大小, 和地址
    let (amt, src) = socket.recv_from(&mut buf)?;

    // 读取的消息翻转一下
    let buf = &mut buf[..amt];
    buf.reverse();

    // 返回翻转后的消息
    socket.send_to(&buf, src)?;
}

Ok(())

跨线程发送函数

果你想通过mpsc发送一个函数, 有一些约束和注意事项:

函数必须是 Send'static 的: 因为要在不同线程之间传递函数, 函数必须实现Send trait, 这确保了函数是线程安全的, 同时, 函数的生命周期必须是'static, 表示函数不引用任何非静态的数据

函数参数和返回值必须实现 Send'static , 如果函数有参数或返回值, 这些参数和返回值也必须满足 Send'static 的要求

fn main() {
    use std::sync::mpsc;
    use std::thread;

    // 定义一个函数类型, 满足 Send 和 'static 约束
    type MyFunction = Box<dyn FnOnce() -> i32 + Send + 'static>;

    // 创建一个 mpsc 通道
    let (sender, receiver) = mpsc::channel::<MyFunction>();

    // 创建一个新线程, 用于接收并执行函数
    thread::spawn(move || {
        // 接收函数并执行
        while let Ok(func) = receiver.recv() {
            let result = func();
            println!("Result: {}", result);
        }
    });

    // 创建一个函数并发送给子线程
    let my_function: MyFunction = Box::new(|| {
        println!("Executing function in another thread!");
        42
    });

    // 发送函数
    sender.send(my_function).unwrap();

    // 等待子线程执行完毕
    thread::sleep(std::time::Duration::from_secs(2));

}

文件操作

use std::fs;

// read 读取字节到 Vector 中
let text = fs::read("./1.txt").unwrap();  // 这里读取到的是 Vec<u8>
println!("读取字节到 Vector 中: {:?}",String::from_utf8_lossy(&text));

// 读取字节并转为 String
let text = fs::read_to_string("./1.txt").unwrap();
println!("读取字节并转为 String: {:?}",text)

Option练习

take

把值从Option中拿出来, 不转移其所有权,内部留下 None, 注意取出来的也是 Option类型而不是内部的T, 因为一个Option中他是无法确定到底是Some还是None

let mut s: Option<String> = Some("hello".to_string());
let b = s;  // 所有权发生了转移
// println!("s: {:?}, b:{:?}", s, b);  // Error: value borrowed here after move

let mut s: Option<String> = Some("hello".to_string());
let b = s.take();  // 使用 take 取出内部的值, 而不转移所有权, 因为不用 take 那么在赋值的时候, 会获得 Option<T> 中T 的所有权, 获取了所有权 Option那保存成了啥! 这是禁止的
println!("s: {:?}, b:{:?}", s, b);  // s: None, b:Some("hello")

is_some/is_none

当Option中有值得时候, 返回对应bool值

let s: Option<u32> = Some(1);
let b: Option<u32> = None;
println!("s.is_some: {}, b.is_some: {}", s.is_some(), b.is_some());  // s.is_some: true, b.is_some: false

contains

判断内部的值是否相等

#![feature(option_result_contains)]
fn main(){
    let n = Some(123);
    println!("n.contains(&123): {:?}", n.contains(&123));  // n.contains(&123): true
}

好像也可以直接使用 ==

let opt1 = Some(5);
let opt2 = Some(5);
let a = opt1 == opt2;

as_ref

&Option<T> 转为 Option<&T>, 把引用本身 转为内部值的引用

// let s = Some("hello".to_string());
// let l = s.map(|x| x.len());  // 如果这样处理会造成所有权转移到闭包内
// println!("s: {:?}, s.len(): {:?}",s, l);  // Error: value borrowed here after move

let s = Some("hello".to_string());
let l = s.as_ref().map(|x| x.len());
println!("s: {:?}, s.as_ref len(): {:?}",s, l);

unwrap_or

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

ok_or

使Option变为Result 类型, 让我们可以进行 ? 操作 而不是会引发panic的unwrap

let s = Some(123);
let s_r = s.ok_or("转换失败")?;
println!("s_r 转换可以使用 ? 操作: {}", s_r);
Ok(())

序列化/反序列化

use serde::{Serialize, Deserialize};

#[derive(Debug,Serialize,Deserialize)]  // 为结构体显式 的添加相应宏, 使其可以支持序列化以及反序列化操作
struct ServerConfig{
    workers:u64,
    ignore:bool,
    auth_server:Option<String>
}

let config = ServerConfig {
    workers: 128,
    ignore: true,
    auth_server: Some("ServerConfigInstance".to_string()),
};

println!("json格式:");
let serialized = serde_json::to_string(&config).unwrap();  // 序列化操作字符串
let serialized_vec = serde_json::to_vec(&config).unwrap();  // 序列化操作为字节
println!("serialized: {}", serialized);
println!("serialized_vec: {:?}", serialized_vec);
let deserializede = serde_json::from_str::<ServerConfig>(&serialized).unwrap();  // 反序列化需要标识反序列化之后的类型
let deserializede = serde_json::from_slice::<ServerConfig>(&serialized_vec);  // 反序列化需要标识反序列化之后的类型
println!("deserializede: {:?}", deserializede);


println!("yaml格式:");
let serialized = serde_yaml::to_string(&config).unwrap();  // 序列化操作
println!("serialized: {}", serialized);
let deserializede:ServerConfig = serde_yaml::from_str(&serialized).unwrap();  // 反序列化需要标识反序列化之后的类型
println!("deserializede: {:?}", deserializede);

Ok(())

异步编程

mio库

mio是一个异步io库

use mio::net::{TcpStream, TcpListener};
use mio::{Events, Token, Interest, Poll};

// 定义一个 Server
const SERVER:Token = Token(0);
const CLIENT:Token = Token(1);

// 创建一个轮训实例
let mut poll = Poll::new().unwrap();

// 创建事件对象, 最大监听128个事件
let mut events = Events::with_capacity(128);

let addr = "127.0.0.1:8000".parse().unwrap();

// 创建服务端 监听连接
let mut server = TcpListener::bind(addr)?;

// 注册事件 到poll中
poll.registry().register(&mut server,SERVER, Interest::WRITABLE).unwrap();

loop {
    // 把事件读到 事件对象中
    poll.poll(&mut events, None).unwrap();

    // 循环获取事件
    for event in events.iter(){
        println!("收到连接");

        // 匹配事件的 TOKEN 进行分发
        match event.token() {
            SERVER => {
                // 通过事件 的方法 获得事件的 "兴趣"
                let is_read = event.is_readable();
                let is_write = event.is_writable();
                println!("is_read: {}, is_write: {}", is_read, is_write);

                // 服务端接收连接
                let connect = server.accept();
                println!("Server Read");

                drop(connect)
            }
            CLIENT => {
            }
            _ => {}
        }
    }
}

Ok(())

async基本使用

使用 async 关键字, 修饰让 function 变为一个符合 Future trait 的状态机, 调用时可以使用 block_on 阻塞 调用

使用 executor::block_on 提交状态机, block_on会阻塞直到Future执行完成

async fn say1(){
    println!("async say 1");
    sleep(std::time::Duration::from_secs(2));
}

let f1 = say1();  // 返回一个实现了 futures trait 的状态机
executor::block_on(f1);

fn say2(){ println!("say 2");}  // 这里由于上面是阻塞用, 所以这里会在调用完 f1 之后才调用
say2();

await基本使用

await也是阻塞执行, 但是会移交线程控制权, 不会阻塞线程

await 会以阻塞方式运行, 并交出线程控制权, join! 宏,会并发异步的调用传入内的状态机, 可以等待多个 future 并发完成;

通过 await 这一操作, 我们允许 join 宏中的其他任务并发的执行

下面在调用 f_3_4 的时候,内部使用了 await 发生了短暂阻塞, 但是不会阻塞线程, 所以线程转而调用 f5(f5 会接管当前线程)

async fn say3(){
    // 睡眠 3 秒
    sleep(std::time::Duration::from_secs(3));  // 注意使用 sleep 会达不到想要的效果,因为这是标准库thread中的sleep 这里会让线程完全阻塞
    println!("async say 3");

}

async fn say4(){
    println!("async say 4");
}

async fn say5(){
    println!("async say 5");
}

// 使用 await 让 say3 和 say4 依照顺序调用, 必须先调用 3, 再调用 4
async fn say_3_4(){
    println!("调用say_3_4");
    say3().await;  // await 会阻塞的方式调用, 并让出线程 但是这里并没有让出线程 是因为 sleep是标准库的sleep
    say4().await;
}

async fn main_async(){
    let f_3_4 = say_3_4();
    let f5 = say5();

    futures::join!(f_3_4, f5);  // 使用 join 调用状态机, 并发的执行
}

let async_func = main_async();
executor::block_on(async_func);  // 阻塞调用状态机

模拟一个简单的future

use std::thread;
use std::time::Duration;

// 定义Future trait
trait SimpleFuture{
    type Output;
    fn poll(&mut self, wake:u64) -> Poll<Self::Output>;
}

// 任务状态
enum Poll<T>{
    Ready(T),
    Pending
}

// 一个类似 future 的结构
struct MySleeper{
    polls:u64,
    wake:u64
}
impl MySleeper{
    fn new(wake: u64) -> Self{
        MySleeper{
            polls: 0,  // 循环次数
            wake  // 序号
        }
    }
}

// 创建一个任务的状态
static mut STATUS:bool = false;

// 实现 future trait
impl SimpleFuture for MySleeper{
    type Output = ();

    // 执行任务, 返回当前future 状态
    fn poll(&mut self, wake:u64) -> Poll<Self::Output>{
        unsafe{
            if STATUS{  // 模拟任务执行完毕
                println!("Ready");
                Poll::Ready(())
            } else {  // 模拟执行任务中
                self.wake = wake;
                self.polls += 1;
                println!("Pending");
                Poll::Pending
            }
        }
    }
}

struct MyReactor{
    wake:u64,
    handle:Option<thread::JoinHandle<()>>
}
impl MyReactor{
    fn new() -> Self{
        MyReactor{
            wake:0,
            handle:None
        }
    }

    fn register_wake(&mut self, wake:u64){
        self.wake = wake;
    }

    fn check_status(&mut self){
        if self.handle.is_none(){
            let _wake = self.wake;
            let handle = thread::spawn(||{  // 创建一个线程模拟future执行
                loop{  // 模拟 一直检测 future 执行状态
                    thread::sleep(Duration::from_secs(5));  // 模拟 future 执行5秒后完成了任务
                    unsafe{  // 完成任务后, 模拟 future 执行完毕调用了其 wake唤醒, 改变 Status
                        STATUS = true;
                    }
                }
            });
            self.handle = Some(handle);
        };
    }
}

struct MyExecutor;
impl MyExecutor{
    // 提交任务, 内部 loop 循环检测 future中的任务执行状态
    fn block_on<F:SimpleFuture>(mut my_future:F, wake:u64){
        loop{
            match my_future.poll(wake) {
                Poll::Ready(_)=>{
                    println!("my future is ok");
                    break  // 如果完成了任务, 直接跳出
                }
                Poll::Pending => {  // 如果正在 Pending 中进入下面不安全的循环体
                    unsafe{
                        while !STATUS {  // 1000 毫秒检测一下future是否完成任务, 模拟等待reactor的通知, 循环 STATUS 为false, 如果为 true 说明reactor发现到 future 执行完毕,那么跳出循环, 到外部循环执行future的poll方法
                            thread::sleep(Duration::from_millis(1000));
                            println!("执行中");
                        }
                    }
                }
            }
        }
    }
}

// 创建一个 future
let mut sleeper = MySleeper::new(5);
let wake = sleeper.wake;

// 创建一个reactor 执行器
let mut reactor = MyReactor::new();
reactor.register_wake(wake);  // 注册
reactor.check_status();  // 开启一个线程进行监听 future执行状态(TODO 注意这里 线程内部内部模拟future执行完毕的操作)

// 使用调度器 执行 future
MyExecutor::block_on(sleeper, wake);

async/await学习

async 有三种主要的使用方法: async fn, async块async闭包, 使用 async 修饰之后的代码块或函数, 变为一个符合 Future trait 的对象

async 主体中使用的任何变量都必须能够在线程之间传输, 因为任何 await 操作都可能导致切换到新线程

使用 RC, &RefCell 或任何其他没实现 Send trait的类型, 包括引用未实现 Sync trait的类型, 是不安全的

在future 需要使用 futures::lock 中的 Mutex 而不是 std::sync::Mutex 中的锁, 来进行数据加锁, 因为使用await是交出线程控制权,使用 std::sync中的锁因为交出了线程控制权所以不会释放锁,其他future在获取锁的时候会导致死锁

异步函数

async fn say_1() -> u8 {
    5
}
let f = say_1();

异步block

单独修饰代码块变为 Future trait

fn say_2() -> impl Future<Output=u8>{
    async {
        5
    }
}
let f = say_2();

async 生命周期

async fn f1(value:& u8) -> u8{
    *value
}
// 上面的例子 等同于下面代码(详见生命周期省略规则),
// async block may outlive the current function, but it borrows `value`, which is owned by the current function
// fn f2<'a>(value: &'a u8) -> impl Future<Output=u8> + 'a {
//     async {
//         *value
//     }
// }

错误调用

fn bad()-> impl Future<Output = u8>{  // 这种调用方式无法编译通过, 因为 x 变量在本函数结束之时就被drop了, 而future返回到了外部,使用的却是被drop的x的引用,造成了垂悬指针
    let x = 5;
    let f = f1(&x);
    return f;
}

正确调用

fn good() -> impl Future<Output = u8> {  // 使用 async 代码块 使其内的 f1 和 变量x 在一个 future内, 这样 内部的x 和f1 是在一个上下文中, 内部的future用多久, 外部的x就存活多久
    return async {
            let x = 5;
            return f1(&x).await  // 使用await 阻塞的运行future, 交出线程控制权, 运行完毕作为返回值, 所以good的签名为 impl Future<Output = u8>, 因为 f1(&x).await阻塞运行直到得到返回值 u8
        }
}
let g = good();

异步move

使用move关键字,强制转移其所有权, 就像在线程中那样, 防止垂悬引用

fn move_block(){
    let hello = "hello".to_string();
    let f = async move {  // 强制移动到了 async 块中(async块内实际上是一个future)
        let s_ = &hello;
        println!("hello: {}",s_)
    };
    // println!("after move hello: {}",hello)  // value borrowed here after move
}

join宏

使用futures中的 join! 宏会让传入的 future 并发的执行

delay_for 可以让future进行阻塞等待(睡眠), 其本身是个future, 需要await执行

注意 join 宏只能在 async 代码块中运行 在外面会出错: "only allowed inside async functions and blocks"

use futures;
use tokio::time::{delay_for, Duration};
use tokio::runtime::Runtime;  // 使用 tokio 中的 block_on 的执行器( ps 因为使用了异步库中的 sleep

async fn func1(){
    // loop {
    //   // 异步async 是用户级线程 所以这里loop之后, 会一直占用该线程, 导致不会执行 yield 进行esp 和pc 的切换
    // }
    delay_for(Duration::from_secs(3)).await;  // 睡眠 3 秒, 移除线程控制权
    println!("this is func1");
}

async fn func2(){
    println!("this is func2");
}

async fn func_1_2(){
    let f1 = func1();
    let f2 = func2();
    dbg!(2);
    futures::join!(f1, f2);
}

let mut run_time = Runtime::new().expect("执行器创建失败");  // 创建一个执行器

run_time.block_on(func_1_2())  // 按照预期, 先打印 func2, 再打印func1

try_join宏

使用try_join, 在执行出错的时候, 会马上返回

try_join 执行结果也是一个 Result 类型, 在内部所有的 future 执行完毕的时候返回执行结果

try_join 内的future 需要返回 Result 类型

use std::fs;
use std::io::{Result};
use futures;
use tokio::time::{delay_for, Duration};
use tokio::runtime::Runtime;  // 使用 tokio 中的 block_on 的执行器( ps 因为使用了异步库中的 sleep

async fn func1() -> Result<()>{  // 需要返回 Result类型
    delay_for(Duration::from_secs(3)).await;  // 睡眠 3 秒
    println!("this is func1");

    // 手动引发一个错误, 并返回上层
    let text = fs::read("./xxxxx.txt")?;

    Ok(())
}

async fn func2() -> Result<()>{
    println!("this is func2");
    Ok(())
}

async fn func_1_2(){
    let f1 = func1();
    let f2 = func2();

    let ret = futures::try_join!(f1, f2);  // 返回一个 Result 类型
    match ret {
        Ok(_) => println!("全部执行成功"),
        Err(e) => (println!("有些 Future 失败~ {:?}", e)),
    }
}

let mut run_time = Runtime::new().unwrap();  // 创建一个执行器

run_time.block_on(func_1_2())  // 按照预期, 先打印 func2, 再打印func1

2024更新

现在不能使用use futures了, 直接使用tokio的即可

use tokio::{join, select};
use tokio::time::{Duration, sleep};
use tokio::runtime::Runtime;


async fn func_1() {
    sleep(Duration::from_secs(3)).await;
    println!("this is func_1");
}

async fn func_2() {
    println!("this is func_2");
}

async fn call_func() {
    let f1 = func_1();
    let f2 = func_2();
    println!("call func start");
    join!(f1, f2);

}

fn main() {
    let runtime = Runtime::new().unwrap();
    runtime.block_on(call_func());
}

select宏

select! 宏会在提交的future 中, 自动的执行 await, 任意分支返回即结束运行

select! 宏中的future必须实现 Unpin traitFusedFuture trait

select! 宏内的 future 需要返回 Fuse(Result) 类型, 使用fuse方法, 且注意需要使用 pin_mut 钉住

select 内的future 在执行 完成 一次之后, 该future之后无法再次执行(当然其他的分支不影响)

select 宏内是按照 future 的可变引用获取的, 通过不获取 future 的所有权,所以在调用select后未完成的future可以反复继续使用

select 完成之后不会再轮训 future, 因此需要实现FusedFuture trait 来跟踪 future是否完成 (对应的Stream流里的future也会要求实现对应FusedStream trait)

select 本身是不会循环的, 可以通过外部的 loop 循环, 如果 select全部完成, 则会走到 complete 分支

用在 select 中的 future 必须实现 Unpin 和 FusedFuture

use futures::{select,future::FutureExt, pin_mut};
use tokio::runtime::Runtime;
use std::io::Result;

async fn func1() -> Result < String > {
    tokio::time::delay_for(tokio::time::Duration::from_secs(3)).await;  // 睡眠3秒
    println!("func1 end");
    Ok("f1".to_string())
};

async fn func2() -> Result < String > {
    println!("func2 end");
    Ok("f2".to_string())
};

async fn async_main() -> Result<()>{
    let f1 = func1().fuse();  // 使用 fuse 方法转换
    let f2 = func2().fuse();

    // 使用 pin_mut  生成了一些代码
    pin_mut!(f1, f2);

    // 使用select! 宏(其内任意future执行完毕,立即返回)
    loop {
        select!{
            a = f1 => println!("select f1 {:?}", a),
            b = f2 => println!("select f2 {:?}", b),  // f2 执行完毕立即返回了
            complete => {println!("全部执行完毕");break},
        }
    }
    Ok(())
}

let mut run_time = Runtime::new().unwrap();
run_time.block_on(async_main());

2024更新 tokio的select

fn main() {
    // tokio 对于自定义的async block, 每个future 执行完之后, 该future 下次循环也还会执行, 所以对于一次性的任务, 需要 手动判断是否执行过了(我们在下面增加了判断标志位 done)
    // 否则会提示: '`async fn` resumed after completion', 异步函数在完成后, 依然被恢复了(继续使用) https://course.rs/advance-practice/select.html#%E6%81%A2%E5%A4%8D%E4%B9%8B%E5%89%8D%E7%9A%84%E5%BC%82%E6%AD%A5%E6%93%8D%E4%BD%9C
    // select 中的分支 可以使用模式匹配, <模式> = <async 表达式> => <结果处理>,
    use tokio::{join, pin, select};
    use tokio::time::sleep;
    use tokio::io::Result;
    use tokio::runtime::Runtime;
    use tokio::time::Duration;

    async fn func1() -> Result<i32> {
        sleep(Duration::from_secs(1)).await;
        println!("func1 1");
        sleep(Duration::from_secs(10)).await;
        println!("func1 2");  // 这个在 "func1 1"  和 "func2 1" 十秒之后打印输出
        Ok(1)
    }

    async fn func2() -> Result<i32> {
        sleep(Duration::from_secs(1)).await;
        println!("func2 1");
        Ok(2)
    }

    async fn async_main() {
        let f1 = func1();
        let f2 = func2();
        // tokio::pin!(f1, f2);  // 值固定在栈上

        let mut f1 = Box::pin(f1);  // 值固定在堆中, 创建了一个新的变量
        let mut f2 = Box::pin(f2);

        let mut f1_done = false;  // f1 是否执行过
        let mut f2_done = false;  // f2 是否执行过

        loop {
            select! {
                Ok(ret) = &mut f1, if f1_done == false => {  // 注意需要使用 &mut f1 可变引用, 因为我们需要 恢复/改变/结束 future的状态, 而不是重新创建一个全新的future
                    f1_done = true;
                    println!("func1 return: {}", ret);
                }
                Ok(ret) = &mut f2, if f2_done == false => {
                    f2_done = true;
                    println!("func2 return: {}", ret);
                }
                else => {
                    println!("所有函数执行完毕");
                    break;
                }
            }
        }
    }

    let rt = Runtime::new().unwrap();
    rt.block_on(async_main());
}

complete/default

使用 ready 可以创建一个有状态的 future

select! 宏调度时会可以增加匹配 有complete(期内future都执行完毕,并且不会再次取)和default(都没执行完毕)两个状态

complete 表示select内future都执行完毕,并且future不会再有相应的后续

default 表示没有分支完成

use futures;
use futures::{select,executor};

async fn async_main(){
    let mut a_fut = futures::future::ready(4i32);
    let mut b_fut = futures::future::ready(7i32);

    let mut num = 0;

    loop {
        select!{
            a = a_fut => num += a,
            b = b_fut => num += b,
            complete => {println!("全部执行完毕");break},
            default => unreachable!()
        };
    };

    println!("经过了select之后: {}", num);
};

executor::block_on(async_main());

Future副本

返回错误类型

use std::io::Result;
use futures::executor;

async fn func1(){
    // "func1"  // 历史问题已经修复: expected `()`, found `&str` 而不是 &'static str 的类型
}

fn func2(){
    // "func2"  // 和上方错误一样: expected `()`, found `&str`
}

executor::block_on(func1());

// !! 注意在 async 中对错误是可以使用 ? 的
async fn func3() -> Result<()> {
    Ok(())
}

async fn func4() -> Result<()> {
    let foo = async {
        func3().await;
        Ok(())
    };
    foo.await
}
executor::block_on(func4());

Send trait

如果一个结构体所有的子类型,都是实现了 Send trait的, 那么其结构体也自动实现 Send trait

rust中有两个并发概念 std::marker 中的 SyncSend trait, 在Rust标准库std::marker模块内部, 就为所有类型默认实现了SendSync, 换句话说, 就是为所有类型设定好了默认的线程安全规则

  • 通过Send 允许在线程间转移所有权, 任何由Send类型组成的类型,也会自动标记为Send, struct A {a,b,c}如果a,b,c是Send类型那么A也是Send类型的
  • Send trait 表明类型的所有权可在线程间传递,几乎所有的 Rust类型都可以Send 但是比如 Rc<T>是不能被 Send的
  • Sync trait 表明一个实现了 Sync类型可以安全的在多个线程中拥有其值的引用, 允许多线程访问, RefCell<T>Cell<T>都不是 Sync 的, Mutex 是Sync的

手动实现 Send 和 Sync 是不安全的, 需要unsafe, 因为这涉及到告诉编译器你确信这个类型在线程间移动或共享是安全的, 由于错误的实现可能导致数据竞争和未定义行为, Rust 要求开发者明确地承担这个责任

#[derive(Default)]
struct NoSend(Rc<()>);  // 一个没有实现 Send 的结构体

async fn bar(){};

async fn foo(){
    // let x = NoSend::default();  // 这行代码是无法编译的, 因为为foo 创建的future的子类型 x 没有实现 Send trait(Rc不是Send trait)
    NoSend::default();  // 这个可以编译成功是因为,该值直接被析构忽略了 let _ = NoSend::default()
    let _ = NoSend::default();  // 同样也是直接被析构了,因为使用了忽略参数 _
    {
        let x = NoSend::default();  // 这样也是可以编译成功的, 编译器优化掉了, 因为x在作用域结束也被析构了, 为foo实现的 future 没有保存这个x
    }

    bar().await;
}

fn run(_:impl Send){};
run(foo())  // 可以运行

BoxFuture

由于future 递归调用的时候是无法确定其大小的, 可以使用 BoxFuture 一个胖指针包裹

use futures::future::{BoxFuture, FutureExt};

// 注意声明时不需要用 async 声明, 内部返回的 async 块需要使用 boxed方法 转为一个 BoxFuture 返回
fn re() -> BoxFuture<'static, ()>{
    async move {
        re().await;
    }.boxed()
}

self的强制解引用

在未实现Copy trait的struct中使用self

struct S;

impl S{
    fn by_ref(&self){}
    fn by_mut_ref(&mut self){}
    fn by_self(self){}
}

// 拥有所有权 三种方法都可以调用
let mut s = S;
s.by_ref();
s.by_mut_ref();
s.by_self();

// 拥有不可变引用
let s = &S;
s.by_ref();
// s.by_mut_ref();  // 这里是无法调用的, 因为签名需要使用的是可变引用
// s.by_self();  // 这里也无法调用, 因为没有实现Copy trait, 所以这里自动解引用获得了所有权,导致外部的变量s成为了野指针

// 拥有可变引用
let mut s = &mut S;
s.by_ref();
s.by_mut_ref();
// s.by_self();  // 这里也无法调用, 因为没有实现Copy trait, 所以这里自动解引用获得了所有权,导致外部的变量s成为了野指针

实现了Copy trait的 struct

#[derive(Copy, Clone)]
struct Y;

impl Y{
    fn by_ref(&self){}
    fn by_mut_ref(&mut self){}
    fn by_self(self){}
}

// 拥有所有权
let mut y = Y;
y.by_ref();
y.by_mut_ref();
y.by_self();

// 拥有引用
let mut y = &Y;
y.by_ref();
// y.by_mut_ref();  // 无法使用, 因为这里签名使用的可变借用, 而变量声明时是不可变的
y.by_self();  // 实现了Copy trait的这里解引用之后直接 Copy 一份变量到方法里了

// 拥有可变引用
let mut y = & mut Y;
y.by_ref();
y.by_mut_ref();
y.by_self();

Pin探索

一篇博客: http://152.69.196.199/index.php/archives/571.html

来自gpt

在Rust编程语言中, Unpin, !Unpin, 和 Pin 是与数据的内存布局和生命周期管理相关的概念, 特别是在处理异步编程和具有内部可变性的数据结构时尤为重要, 以下是这三个概念的解释及其关系

Unpin

概念: Unpin 是一个自动派生的标记特质(trait), 表示一个类型的数据可以自由地被移动(move)而不影响其内部状态, 几乎所有类型默认都实现了 Unpin, 意味着它们的内存地址可以改变而不会有问题,

行为: 当一个类型实现了 Unpin, 编译器允许在不考虑其内部指针或引用的情况下移动该类型的实例, 这对于普通的数据结构非常合理, 因为它们通常不包含指向自身的引用,

!Unpin

概念: 当一个类型明确表示不实现 Unpin 特质时, 我们可以认为它是 !Unpin 类型, 这意味着该类型包含有自引用(例如, 内部字段是指向结构体其他部分的引用或指针), 因此它的内存地址必须保持固定, 不能随意移动,

行为: 如果一个类型没有实现 Unpin, 那么在某些上下文中, 如异步编程中使用 Future 时, 就必须使用 Pin 来确保该类型实例的内存地址不会改变, 从而保护其内部的自引用安全,

Pin

概念: Pin 是一个结构体, 用来确保其包裹的值(通常是 !Unpin 类型)在内存中的地址不会改变, 它通过持有对数据的引用并在编译时强制执行固定规则, 防止被固定的值被意外移动,

用途: 在异步编程中, 很多Future或者包含自引用的数据结构需要使用 Pin 来保证它们的地址固定, 以便安全地在异步执行过程中访问和修改, Pin 提供了对不可移动类型的安全处理方式, 允许在不违反借用规则的前提下操作这些类型,

关系

Unpin vs !Unpin: 这是对类型是否可以被移动的分类, Unpin 类型可以自由移动, 而 !Unpin 类型则因内部结构限制不能随意移动,

Pin 与 Unpin/!Unpin: Pin 是用来处理 !Unpin 类型的一种机制, 确保它们在内存中的位置固定, 即使它们参与了异步执行或其它可能导致移动的操作, 对于实现了 Unpin 的类型, 虽然也可以使用 Pin, 但这通常是不必要的, 因为它们本来就可以安全移动,

综上所述, Unpin 和 !Unpin 描述了数据类型在内存管理上的可移动性, 而 Pin 是一种工具, 用于确保那些地址必须固定的类型在使用时保持内存地址的不变性,

出现的问题

// 创建一个自引用变量

#[derive(Debug)]
struct Test {
    a: String,
    b: *const String,
}

impl Test {
    fn new(txt: &str) -> Self {
        Test {
            a: String::from(txt),
            b: std::ptr::null(),
        }
    }

    fn init(&mut self) {
        let self_ref: *const String = &self.a;
        self.b = self_ref;
    }

    fn a(&self) -> &str {
        &self.a
    }

    fn b(&self) -> &String {
        unsafe {&*(self.b)}
    }
}

let mut test1 = Test::new("test1");
test1.init();
let mut test2 = Test::new("test2");
test2.init();

println!("test1.a: {}, test1.b: {}", test1.a(), test1.b());  // test1.a: test1, test1.b: test1
println!("test2.a: {}, test2.b: {}", test2.a(), test2.b());  // test2.a: test2, test2.b: test2
// 使用swap()函数交换两者, 这里发生了move
std::mem::swap(&mut test1, &mut test2);
test1.a = "I've totally changed now!".to_string();
println!("a: {}, b: {}", test2.a(), test2.b());  // a: test1, b: I've totally changed now!

// 出问题了! 问题出在哪? 原因是Test结构体中的字段b是一个指向字段a的指针, 它在栈上存的是字段a的地址
// 通过swap()函数交换两个Test结构体之后, 字段a,b分别移动到对方的内存区域上, 但是a和b本身的内容没有变
// 也就是指针b依然指向的是原来的地址, 但是这个地址现在已经属于另外一个结构体了!

使用 Box::pin? 具体还没研究, 下面是不能改变指针数据了, 但是 改变代码中的写法和顺序, 他又被修改了

let mut test1 = Test::new("test1");
test1.init();
let mut test1 = Box::pin(test1);


let mut test2 = Test::new("test2");
test2.init();
let mut test2 = Box::pin(test2);


println!("test1.a: {}, test1.b: {}", test1.a(), test1.b());  // test1.a: test1, test1.b: test1
println!("test2.a: {}, test2.b: {}", test2.a(), test2.b());  // test2.a: test2, test2.b: test2

std::mem::swap(test1.deref_mut(), test2.deref_mut());
test1.a = "I've totally changed now!".to_string();
println!("a: {}, b: {}", test2.a(), test2.b());  // a: test1, b: test1

PhantomPinned

我们可以通过增加 PhantomPinned 这个实现了 !Unpin 的struct 来让类型 !Unpin Pin到堆上 这里使用Box::pin()把Test钉在了堆上, 取消注释任意一行都会编译不通过, 因为Test是!Unpin的,

use std::pin::Pin;
use std::marker::PhantomPinned;

#[derive(Debug)]
struct Test1 {
    a: String,
    b: *const String,
    _marker: PhantomPinned,
}

impl Test1 {
    fn new(txt: &str) -> Pin<Box<Self>> {
        let t = Test1 {
            a: String::from(txt),
            b: std::ptr::null(),  // b这里是一个空指针
            _marker: PhantomPinned,
        };
        let mut boxed = Box::pin(t);

        let self_ptr: *const String = &boxed.as_ref().a;  // 获得 Pin 内部的 String 的指针 下面赋值给 空指针 b
        unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };

        boxed
    }

    fn a<'a>(self: Pin<&'a Self>) -> &'a str {
        &self.get_ref().a
    }

    fn b<'a>(self: Pin<&'a Self>) -> &'a String {
        unsafe { &*(self.b) }
    }
}

let mut test1 = Test1::new("test1");
let mut test2 = Test1::new("test2");

println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());

// std::mem::swap(test1.get_mut(), test2.get_mut());
// std::mem::swap(&mut *test1, &mut *test2);
println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());

generator

目前版本使用 generator 需要在文件第一行加入 #![feature(generators, generator_trait)] 开启才可以使用

使用 resume方法会让程序的执行流程跳回到生成器中执行代码, 并且在遇到yield关键字时跳出生成器, 返回给调用者

生成器属于一种半协程(Semi-Coroutine), 半协程是一种特殊的且能力较弱的协程, 它只能在生成器和调用者之间进行跳转, 而不能在生成器之间进行跳转

基本使用

use std::ops::{Generator, GeneratorState};
use std::pin::Pin;

let mut generator = || {
    println!("执行中");
    yield 1;
    return "foo"
};

std::thread::sleep(std::time::Duration::from_secs(1));

match Pin::new(&mut generator).resume(()) {
    GeneratorState::Yielded(1) => {}
    _ => panic!("unexpected return from resume"),
}
match Pin::new(&mut generator).resume(()) {
    GeneratorState::Complete("foo") => {}
    _ => panic!("unexpected return from resume"),
}

将生成器用作迭代器

将生成器用作迭代器, 如果只关注计算的过程, 而不关心计算的结果, 可以将Return设置为(),

只保留Yield的类型也就是Generator<Yield=T, Return=()>那么生成器就可以化身为迭代器

由于生成器是延迟计算或惰性计算, 所以往往比迭代器性能更高

fn up_to1() -> impl Generator<Yield=u64, Return=()>{
    || {
        for i in 0..100{
            yield i;
        }
        return ();
    }
}
let mut gen = up_to1();

loop {
    match Pin::new(&mut gen).resume(()) {
        GeneratorState::Yielded(i) => println!("执行中: {}", i),
        _ => {
            println!("完毕");
            break
        }
    }
}

用生成器模拟Future

不关心过程, 只关注结果, 则可以将 Yield 设置为单元类型, 只保留 Return 的类型

Generator<Yield=(), Return=Result<T, E>>, 生成器就可以化身为Future

fn up_to2(num:u64) -> impl Generator<Yield=(), Return=Result<u64, ()>>{

    move || {
        let mut c = 0;
        for i in 0..num{
            c += i;
            yield ();
        }
        return Ok(c);
    }
}

let mut gen = up_to2(100);
loop {
    match Pin::new(&mut gen).resume(()) {
        GeneratorState::Complete(i) => {
            println!("执行完毕: {:?}", i);
            break;
        }
        _ => {// 忽略执行中
        }
    }
}

tokio Implementing Future

tokio::main

#[tokio::main] 是一个宏, 宏下面的 async 函数, 是作为宏的 Future 输入, tokio::main 做的主要工作是 builder 一个 runtime , 然后启动 block_on 函数, 把 Future 包装进入 Block_on

// 比如: #[tokio::main]
//       async fn main() {
//          println!("hello");
//       }

// 会被转化为: 
fn main() {
        let mut rt = tokio::runtime::Runtime::new().unwrap();
        rt.block_on(async {
            println!("hello");
        });
}

参考资料

参考资料 https://tokio.rs/tokio/tutorial/async

Future的所有者负责通过轮询Future来推进计算, 这可以通过调用Future::poll来实现

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};

// 1. 实现一个 Future struct 等到特定的时间, 输出一些字符串, 并结束轮训
struct Delay {
    when: Instant,
}

impl Future for Delay {
    type Output = &'static str;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str>{

        // Future 会一直循环轮训该状态机

        println!("轮训中---");

        if Instant::now() >= self.when {
            println!("Hello world");
            Poll::Ready("done")
        } else {
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

let when = Instant::now() + Duration::from_millis(10*99999);
let future = Delay { when };  // 实例化了一个 future

let out = future.await;  // 并调用了await
assert_eq!(out, "done");

上面的 Future 编译器会生成大概 如下的实现(一个未来可能状态的枚举, 未来从 state0 开始)

如果 future 没有完成, 那么会收到 Poll:pending表名该future将在稍后的时间完成, 调用者应稍后再次调用 poll

我们可以对任何实现Future的值调用.await, 用一个异步函数会返回一个实现Future的匿名类型

enum MainFuture {
    // Initialized, never polled
    State0,
    // Waiting on `Delay`, i.e. the `future.await` line.
    State1(Delay),
    // The future has completed.
    Terminated,
}

impl Future for MainFuture {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
            -> Poll<()>
    {
        use MainFuture::*;

        // 循环内部查询执行结果 并状态转移
        loop {
            match *self {
                State0 => {

                    // 实例化一个 future
                    let when = Instant::now() + Duration::from_millis(10);
                    let future = Delay { when };

                    // 状态转移
                    *self = State1(future);
                }
                State1(ref mut my_future) => {

                    // 通过 Pin 钉住执行 实例化的 future
                    match Pin::new(my_future).poll(cx) {
                        Poll::Ready(out) => {
                            assert_eq!(out, "done");
                            *self = Terminated;
                            return Poll::Ready(());
                        }
                        Poll::Pending => {
                            return Poll::Pending;
                        }
                    }
                }
                Terminated => {
                    panic!("future polled after completion")
                }
            }
        }
    }
}

函数与闭包, 函数项和函数指针

函数项和函数指针

函数项是 零大小类型, 能够享受编译器对零大小类型的一切优化

手动声明变量的类型, 函数项会隐式转为函数指针, 占用 1usize 大小, 无法享受零大小类型优化

fn f(){
    dbg!(1);
}

let a = f;  // 函数项
println!("函数项 fn1 {:?}", std::mem::size_of_val(&a));  // 函数项 fn1 0

let a:fn() = f;  // 标注变量的类型, 函数项转为函数指针, 类型会变大了
println!("函数指针 fn2 {:?}", std::mem::size_of_val(&a));  // 函数指针 fn2 8

let c1 = || {};
c1();
c1();

闭包

FnOnce 的闭包, 如果没有捕获环境变量, 编译器会把它看做函数指针fn(T)来看待

  • 如果没有任何捕获变量, 编译器生成的默认结构体则实现FnOnce
  • 如果有捕获变量, 并且会对捕获变量进行修改, 则实现FnMut
  • 如果有捕获变量, 但是没有对捕获的变量进行修改, 则实现 Fn

FnOnce/FnMut/Fn 这三者的Trait关系是依次继承, 正好对应所有权语义三件套

let c1 = || {};
let c2 = || {};
let v = [c1, c2];  // 没有报错, 说明是ok的, 是同一个类型

let i = "c3";
let c3 = || i;
// let v = [c1, c2, c3];  // Error 因为捕获了环境变量, c3并没有被当做函数指针 和c1 c2 是不同的

如果环境变量实现了Copy, 闭包如果以可变借用方式捕获环境变量, 并对其进行修改, 则 闭包本身不会实现Copy防止出现多个闭包内的多个可变借用对环境变量进行修改

fn foo<F:Fn() + Copy>(f:F) {
    f()
}`
let s1 = "123".to_string();
let f1 = || println!("{}", s1);
foo(f1);

如果环境变量自身是Move语义, 则闭包内捕获环境变量的操作涉及修改环境变量或者消耗环境变量, 则闭包自身不会实现Copy, 防止多次修改或者消耗

let s2 = "123".to_string();
let f2 = move || println!("{}", s2);
// foo(f2);  // 由于使用了move关键字, 闭包内捕获环境变量move语义变量 s2时 转移了所有权, 对环境变量进行了消耗, 则闭包不会实现Copy, 所以 foo的泛型签名无法满足

如果所有捕获变量均实现了 Sync, 则闭包实现Sync,

如果环境变量 都不是 唯一不可变引用方式捕获的, 并且都实现了 Sync, 则闭包实现Send

如果环境变量是以 唯一不可变引用 可变引用Copy或者Move所有权捕获的, 那闭包实现Send

Copy和Clone 行为

Rust默认用栈来管理内存, 按位复制 != 栈复制, 因为Rust用栈来管理内存, 所以大部分情况下Rust中按位复制是在栈中复制,但是不代表 按位复制一定等于栈复制

Copy主要对应栈上的数据(但是copy并不总是在栈上发生, 如上), 而Clone 一般对应的是 堆上的数据(但也不全是)

如果要实现 Copy 那么也必须实现 Clone

Copy是编译器的行为, 隐式行为, 相当于一个按位复制; Copy行为 是无法被重载的, 自己实现的Copy和Clone 无法改变编译器的默认行为, 除非自己显式调用

虽然每个类型实现 Clone 时内部的行为不同, 但是我们要从语义层面理解, 它旨在让我们多次使用一份数据所使用的的方法

// 1. 自己实现的Copy和Clone 无法改变编译器的默认行为, 除非自己显式调用
struct A;
impl Clone for A{
    fn clone(&self) -> Self {
        println!("发生了一次 clone");
        *self
    }
}
impl Copy for A{}

let a = A;
let b = a;  // 这里编译器会隐式调用 let b = a.clone(), 但是并没有输出我们自定义的clone, 而是编译器实现覆盖了我们的实现
let b = a.clone(); // 但是如果是显式的调用, 那么会输出打印我们自定义的clone行为, ! 但是这个和使用 = 赋值是没关系的, 这个是我们自己调用方法触发的输出打印

理解按位复制

#[derive(Copy, Clone)]
struct B(i8, i32);
let b1 = B(1, 2);
let b2 = b1;  // 按位复制, 复制后 b1 和b2 完全相同,包括内存对齐填充的padding部分
let b3 = B(b1.0, b1.1);  // 逐成员复制, 非按位复制,  b3 和 b1 他们的padding部分不一定相同, 相当于创建了一个新的结构体, 重新进行对齐

UnSafe按位复制

UnSafe按位复制, 会出现的问题, 比如两个指针同时指向一块堆内存

use std::{ptr, mem};
let mut s1 = String::from("abcde");
let l = mem::size_of_val(&s1);
println!("len: {}", l);  // 3 usize len: 24, 指针, 容量, 长度

// {  // 我们尝试增加作用域, 那么这里会出现 double free 问题, 因为我们内部创建了一个s2的String, 且s2的和s1指向同一个堆内存区域; s1和s2其实是一个胖指针, 内部通过Vec<u8>管理; 在作用域消失之后, s2会的drop实现会导致源s1也失效
    let mut s2 = String::new();
    unsafe {
        ptr::copy(&s1, &mut s2, l);
    }
    println!("s2_ptr: {:?}", s2.as_ptr());

    // // 如果不使用作用域进行删除 而使用drop_in_place, 那么编译器会检查这个变量是否需要删除, 我们可以通过needs_drop 来得到该作用域是否会删除变量,
    // unsafe {
    //     use std::mem::needs_drop;
    //     assert_eq!(needs_drop::<*mut u8>(), false);  // 说明这里不需要检查, 编译器认为裸指针这里不是很重要, 不需要drop
    //     ptr::drop_in_place(s2.as_mut_ptr())
    // }

// }

println!("s1_ptr: {:?}", s1.as_ptr());
s1.push_str("f");

println!("s1: {}", s1);
println!("s2: {}", s2);  // abcde 至于为什么没有f 是因为String内部是一个Vec(指针, 容量, 长度), 在ptr::copy的时候 len的长度是 5个, 导致 s2 的长度也为5个

按位复制 != 栈复制

下面是一个使用 Box在堆上开辟内存 堆上按位复制的例子

let b1 = Box::new(RefCell::new(1));
let b2 = Box::new(RefCell::new(2));

Move语义和析构

Move语义 从语义层面 和所有权相关的, 对于开发者来说, 需要掌握语义和所有权语义模型即可

Move语义 一般对应 栈上保存了堆上真实数据的指针, 当然也并不全是, 比如下面

发生Move时, 堆上数据并没有移动位置, move只是转移了栈上的数据

Move后的变量, 会变为未初始化的状态, 本文中有详细的demo

只有除了Box, 其他的Rc和Arc等等 都不能使用 解引用移动

let s1 = "123".to_string();  // 或者使用 Box::new("123".to_string()) 也能做该实验
println!("&s {:p}", &s1);  // 这个打印的指针 仅仅 是 栈上数据 s 的指针, 而不是堆上真实数据的指针
println!("s.as_ptr {:?}", s1.as_ptr());  // 这个获取到的是堆上数据的指针

let s2 = s1;  // 解引用 发生move, 所有权转移
println!("s2.as_ptr {:?}", s2.as_ptr());  // 查看堆上指针得知, 堆上数据并没有移动位置, move只是转移了栈上的数据

Move并不全是涉及堆

栈上的数据也会发生move

struct S{
    n: String
}

let i1:u8 = 2;
let i2:u8 = 2;
let s1 = S{n:"123".to_string()};
let s2 = S{n:"2233".to_string()};
let i3:u8 = 2;

println!("{:p}", &i1);  // 0x7ffd6a58b64e
println!("{:p}", &i2);  // 0x7ffd6a58b64f
println!("{:p}", &s1);  // 0x7ffd6a58b650  这里紧贴着 i2, 说明变量也是在栈上
println!("{:p}", &s2);  // 0x7ffd6a58b680
println!("{:p}", &i3);  // 0x7ffd6a58b6bf

Scope和NLL

一个花括号 可以看做一个块表达式, 一个新的此法作用域, 对应的一个栈帧 stack frame, 也可以看做一个生命周期

词法作用域生命周期

let mut v = vec![];
v.push(1);
{
    v.push(2);
    println!("{:?}", v);
}
println!("{:?}", v);

NLL 非词法作用域生命周期检查

非词法作用域的借用检查对可变引用只会检查到最后一次使用的地方

在以前版本 生命周期检查发生在 hir阶段, 只能进行词法作用域生命周期检查, 这样是可以编译通过的, 但是现在改在了 mir阶段 进行了非词法作用域生命周期检查

let mut v:Vec<i32> = vec![];
let vv = &v;
{
    // v.push(2)  // 这是禁止的, 可变借用和不可变借用生命周期发生了交叉
};
vv;

生命周期和生命周期参数

晚限定: late bound

struct A<T>(T);  // 结构体在声明的时候, 泛型是没法确定的, 只有在实际调用的时候才知道它限定了一个具体的什么类型

let a = A(1);  // 不限定, 不过这样不代表我们不需要类型, 而是编译器自动推导添加了
let a = A::<i32>(2);  // 晚限定 手动添加类型标注
let a = A::<String>(String::from("123"));  // 晚限定 手动添加类型标注

对于生命周期来说 'a 其实也可以看做一个泛型参数, 它只是也仅仅是开发者和编译器之间的约定

该约定 也仅仅 只限于 当前词法作用域内, 至于外部 谁长谁短, 在当前词法作用域下编译器并不考虑

生命周期参数不会把引用入参的生命周期变长或者变短, 也不会把出参的生命周期变长或者变短, 他只是一个泛型约定

'a: 'c 这里 'a'c 要长, 'a 可以转为 'c 而反之不可以, 'a 可以看做是'c 的子集, 这里也只是定义了入参和出参的关系, 但是在使用的时候才知道具体的生命周期信息

fn the_longest<'c, 'a: 'c, 'b: 'a >(s1: &'a str, s2: &'b str) -> &'c str {
    // 生命周期让开发者和编译器约定 'c 最短, 'a 比 'c 长, 'b 比 'a 长, 'a 和 'b 都来自外界参数
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

let s1 = "1".to_string();
let s1_r = &s1;
{
    let s2 = "2".to_string();
    let s2_r = &s2;
    the_longest(s2_r, s1_r);
    the_longest(s1_r, s2_r);  // 虽然签名时 'b: 'a, 第二个形参比第一个形参长, 但是在外部 编译器并不关心 传入的参数到底谁长谁短, 这里明显s2_r短, 但是没有关系
}

早限定: early bound

early bound 提升 了非词法作用域 NLL的作用界限, 就是不在当前词法作用域对生命周期标注生效的

如果 我们声明生命周期参数是在 impl<'a> 声明的, 那么这个就是 early bound, 如果是在 fn xx<'a> 则不是, 因为后是调用的时候才知道具体的生命周期

需要解决的问题

因为生命周期省略的原因, 导致 编译器用到了错误的生命周期

怎么和本文中的生命周期缺陷有点相似哈

struct Buffer_1 {
    buf: Vec<u8>,
    pos: usize
}
impl Buffer_1 {
    fn new() -> Self {
        Self {
            buf: vec![1, 2, 3, 4, 5, 6],
            pos: 0
        }
    }
    fn read_bytes<'a>(&'a mut self) -> &'a [u8] {
        self.pos += 3;
        &self.buf[self.pos - 3..self.pos]
    }
}
let mut buf = Buffer_1::new();
let b1 = buf.read_bytes();  // b1 变量 相对最后一次可变借用发生在这里 暂时的
let b2 = buf.read_bytes();  // b2 变量 相对最后一次可变借用发生在这里 暂时的

// 理论上 b1 和 b2 不会发生交叉, 是没有影响的, 因为我们只是 暂时可变借用了 buf, 随后只返回了一个 共享引用, 但是由于我们的签名 read_bytes<'a>(&'a mut self) -> &'a [u8]
// 会使编译器推断 输入和输出的 生命周期一样长, 导致 &mut self 和 &[u8] 一样长, b1 和 b2 都用到了 &mut self, 被拉长到下一行代码 发生了交叉
// 和Rust死灵书中 https://nomicon.purewhite.io/lifetime-mismatch.html#生命周期的局限    有一些相同的地方
// println!("b1: {:?}, b2: {:?}", b1, b2);  // 解开注释

如果我们对上面程序解糖:

ps: 通过下面的解糖, 生命周期缺陷也可以理解了

'b: {
    let mut buf = Buffer_1::new();
    'c: {
        let b1:&'c [u8] = Buffer_1::read_bytes::<'c>(&'c mut buf);  // 生命周期系统被迫将 &mut buf 扩展为了 'c 的生命周期
        'd: {
            let b2: &'d [u8] = Buffer_1::read_bytes::<'d>(&'d mut buf);  // 非词法作用域生命周期检查 NLL 时, 编译器发现 &'d mut buf 在 &'c mut buf 里, 就禁止了
                                                                         // 通过原变量再次出借, 和生命周期缺陷那一节 有相同的地方

            println!("b1: {:?}, b2: {:?}", b1 'c, b2 'd);
        }
    }
}

使用 early bound 手动注明生命周期

根据我们真正关心的引用语义, 这个程序显然是正确的, 但是生命周期系统太蠢了, 无法处理这个问题, 需要我们改变一下代码, 并进行手动标注

我们希望从外部接收数据, 并手动改造一下, 让其 输出的生命周期 和 外部接收的数据的生命周期一样长

在impl块中声明的泛型和生命周期参数适用于整个impl块内的所有方法, 这意味着这些参数对impl块内定义的所有方法都是可见的, 并且可以被所有这些方法使用, 提升其生命周期

struct Buffer_2<'a> {
    buf: &'a Vec<u8>,
    pos: usize
}
impl<'b, 'a: 'b> Buffer_2<'a> {
    // 提前 "约定" 生命周期关系, 我们在impl块上进行提前约束
    // 'a 比 'b长, 下面函数返回的 子slice是从 v来的, v的生命周期是'a,  子串生命周期也是 'a, 是正确的
    // 而 self 的生命周期是 'b
    fn new(v: &'a Vec<u8>) -> Self {
        Self {
            buf: v,
            pos: 0
        }
    }
    fn read_bytes(&'b mut self) -> &'a [u8]{
        self.pos += 3;  // 最后一次可变借用, 但是 'a比'b长 所以没有发生交叉
        &self.buf[self.pos - 3..self.pos]
    }
}
let v = vec![1, 2, 3, 4, 5, 6];
let mut buf = Buffer_2::new(&v);
let b1 = buf.read_bytes();  // 可变借用并不会被拉长生命周期, 我们提前标注了返回的生命周期是比 self长
let b2 = buf.read_bytes();
println!("b1: {:?}, b2: {:?}", b1, b2);

同样的, 我们解糖

'h: {
    let v:&'h Vec<i32> = &vec![1, 2, 3, 4, 5, 6];
    'i: {
        let mut buf = Buffer_2::new::<'h>(v);
        'j: {
            let b1:&'h [u8] = Buffer_2::read_bytes(&'j mut buf);  // 和编译器"约定", 输入的生命周期比 输出的短, &mut self 没有被拉长, 这里暂时借用后就是最后一次使用
            'k: {
                let b2:&'h [u8] = Buffer_2::read_bytes(&'k mut buf)  // 暂时借用后就是最后一次使用, &mut self 的生命周期在这一行方法内的 self.pos += 3;之后就结束了
            }
            println!("b1: {:?}, b2: {:?}", b1 'h, b2 'h);
        }
    }
}

泛型研究T 和 &T

T&T&mut T都是无限集, 因为可以无限次地借用一个类型;

T&T&mut T的超集;

&T&mut T是不相交的集合

trait Trait{
    fn f(self);
}

impl<T> Trait for fn(T){
    fn f(self){
        println!("1");
    }
}

impl<T> Trait for fn(&T){
    fn f(self){
        println!("2");
    }
}

let f1:fn(_) = |_:u8| {};
let f2:fn(_) = |_:&u8| {};
let f3:fn(&_) = |_:&u8| {};

f1.f();  // 1
f2.f();  // 1  T可以代表 T也可以代表 &T, 而&T只能代表&T, 这里我们手动指明 f2的签名 为 _:&u8 但是函数注解是 _ , 导致了 &u8 整个签名被看为了 _, 也就是 `&T` 整体 被看为了 T, 所以触发的是打印1 的方法
f3.f();  // 2  这里在 实例化闭包的时候 let f3:fn(&_) = |_:&u8| {}; 变为了 let f3:fn(&'a _) = |_:&'a u8| {}; 特例为了 &T

冲突的情况

trait Trait1 {}
impl<T> Trait1 for T {}
// impl<T> Trait1 for &T {} // compile error 冲突了, 因为 上面 T 已经包含了 &T
// impl<T> Trait1 for &mut T {} // compile error 冲突了, 因为 上面 T 已经包含了 &mut T

trait Trait2 {}
impl<T> Trait2 for &T {} // 没问题 因为是不相交的集合
impl<T> Trait2 for &mut T {} // 没问题

static生命周期的思考

&'static TT: 'static是不同的

  • T: 'static; 应该读作"T被'static lifetime约束"
  • T: 'static 可以拥有任意的lifetime, 如果T: 'static, 那么T: 'a, 因为任意'a均满足'static >= 'a, 所以 'static 是 'a的子集
  • T: 'a包括了所有&'a T, 但反过来是不成立的,
  • T: 'a&'a T更通用和灵活; `T: 'a接受独立类型, 含有引用的独立类型, 以及引用; &'a T只接受引用
// 通过static 限制, 限制入参必须 为 &'static 生命周期
// 当然 对于泛型 T 也可以认为 是 &'static
fn call<T:'static>(f:T) -> T {
    f
}
let i = 123;
let ref_i = &123;
// 调用得知 i 和 &i 都是符合 'static的
call(i);
call(ref_i);

高阶生命周期

编译器在跨函数的时候, 不会把两个函数连起来分析, 他只会分析单个当前函数的当前作用域, 当你的引用超出范围, 就会报错, 这时候需要我们手动对生命周期进行"约定", 告诉借用检查器, 两个函数分别用什么样的生命周期

需要解决的问题

trait Something<T>{
    fn do_sth(&self, value:T);
}

impl<'a, T:Debug> Something<T> for &'a usize {  // // 为所有 usize的引用实现这个 trait
    fn do_sth(&self, value: T) {
        println!("value: {:?}", value);
    }
}

// fn foo<'a>(b:Box<dyn Something<&'a usize>>){  // 函数没有返回任何引用, 没有内存安全问题, 但是却编译不过, 如果我们没有标注 'a, 编译器会让我们添加 'a生命周期参数(注意! 添加'a是一个错误的生命周期)
//     'x: {                                     //   编译器在编译foo函数调用的时候, 它会判断foo函数内的引用情况, 函数内有一个局部变量, 这个局部变量会在函数调用完毕后析构, 而我们把这个局部变量的引用传入了一个要求更长的方法内
//         let u:usize = 123;        // 一个局部变量
//         b.do_sth(&u);             // 使用了一个局部变量的引用  会实例化为 &'x u   ('x假定当前作用域)
//                                   // 编译器在 对一个局部变量引用的话, 它会以当前的生命周期来判断
//                                   // 但是我们传进来的b 的生命周期是很长的, b在调用方法的时候要求'a的生命周期, 而内部的局部作用域我们假定为 'x, 我们生成了一个局部作用域的变量 &'x u, 显然'x 比 'a 短, 'x无法变为'a, 无法通过编译
//     }
// }

使用晚限定 late bound 标注高阶生命周期

fn foo(b:Box<dyn for <'b> Something<&'b usize>>){  // 使用晚限定 late bound, 不让编译器根据当前作用域去实例化生命周期, 编译器在编译foo的时候, 不会进行借用检查
    'x: {                                          // 而是在foo函数 b 调用方法的时候, 才去根据定义的生命周期参数约定 做检查, 而不是在调用 foo函数 的时候检查
         let u:usize = 123;
         b.do_sth(&u);                       // 晚限定, 这里实例化的是 &'b u, 而不是使用局部作用域的生命周期&'x u
    }
}

foo(Box::new(&0usize));

闭包生命周期

// 1. 下面的闭包 以及执行, 好像没什么问题, 我们生成一个闭包, 接收一个引用, 原地返回, 但是编译器提示 "生命周期可能不足"
// 好像已经修复了 可以正常编译了
let f = |&x| x;
let i = &3;
let j = f(i);

// 2. 如果是以前版本需要下面的方法修复, 重新定义一个函数, 使用高阶生命周期, late bound 在编译调用的时候, 再去检查生命周期
fn annotate<F, T>(f:F) -> F
where for<'a> F: Fn(&'a T) -> &'a T  // 'a 写在函数声明的这里, 表示在 编译调用内部返回的 f 的时候 才会检查生命周期, 传出的 T 要小于等于 T
{
    f  // 返回闭包到外部
}
let f = annotate(|x:&i32|x);
let j = f(i);


// 3. 上面的 也可以改为 早限定 early bound, 在 编译调用annotate_early 的时候 检查生命周期, 而不是在 编译内部的返回值 f的时候
// 表明了 泛型T 要比 'a 长
fn annotate_early<'a, T: 'a, F>(f: F) -> F
where F: Fn(&'a T) -> &'a T
{
    f
};

let f = annotate_early(|x:&i32|x);
let j = f(i);

第三方库的 Once Cell, Lazy

OnceCell可以存储任意的非Copy类型, 且只分配一次, 并提供对存储内容的直接访问

once_cell提供了unsync::OnceCellsync::OnceCell这两种Cell(字面意思, 前者用于单线程, 后者用于多线程)

  • set 安全的设置全局变量
  • get 获得已经设置的全局变量
  • get_or_init 获得静态变量中的值, 如果内部是空的, 则用 Fn 初始化, 多线程可以使用不同的初始化函数同时调用, 但是只有一个函数会被执行

Once Cell

安全的初始化全局变量

use once_cell::sync::{OnceCell, Lazy};
#[derive(Debug)]
struct MyStruct1 {
    name: String
}
impl MyStruct1 {
    fn new(s: &str) ->Self {
        println!("call new: {}", s);
        Self {
            name: s.into()
        }
    }
}

// 声明一个全局的静态变量
static MYSTRUCT1INSTANCE:OnceCell<MyStruct1> = OnceCell::new();
// 通过 set 设置全局变量
MYSTRUCT1INSTANCE.set(MyStruct1::new("zs"));
// MYSTRUCT1INSTANCE.get().unwrap().name;

使用 get_or_init 创建全局变量

// 声明一个全局的静态变量
static MYSTRUCT2INSTANCE:OnceCell<MyStruct1> = OnceCell::new();
// 循环创建(只会创建一次)
for _ in 0..10 {
    MYSTRUCT2INSTANCE.get_or_init(||{
        MyStruct1::new("ls")
    });
}

Lazy

延迟创建全局变量

use once_cell::sync::{OnceCell, Lazy};

// 使用 Lazy::new 延迟创建全局变量
static MYSTRUCT3INSTANCE:Lazy<MyStruct1> = Lazy::new(||{
    MyStruct1::new("ww")
});

println!("ww 还没创建好");
for _ in 0..3 {
    println!("ww 只有在调用的时候才会创建 {:?}", MYSTRUCT3INSTANCE.name);  // 虽然在循环内, call new: ww 只会调用一次
};

与lazy_static不同的是, Lazy可以使用局部本地变量

let v = vec![5, 8, 9];
let thunk = Lazy::new(|| -> i32{
    v.iter().sum::<i32>()
});
assert_eq!(*thunk, 6);

静态变量存储在结构体中

也可以把静态变量, 惰性的创建在结构体中

直接使用 OnceCell 当做 字段中的类型

struct Ctx{
    config: OnceCell<String>,
    config_path: String
}

impl Ctx{
    pub fn get_config(&self) -> Result<&str, std::io::Error> {
        let cfg = self.config.get_or_try_init(|| {
            fs::read_to_string(&self.config_path)
        })?;
        Ok(cfg.as_str())
    }
}

标准库的Once和Lazy

!! 注意: 从 Rust 1.70 开始,once_cell crate 的 once_cell::sync::OnceCell 集成到标准库中, 成为 std::sync::OnceLock

let v = OnceCell::new();  // 单线程版本
v.get_or_init(||1);
v.get_or_init(||2);  // 写入无效
v.set(3);  //  写入无效
dbg!(v);  // 1

let v = OnceLock::new();  // 多线程版本
v.get_or_init(||1);
v.get_or_init(||2);  // 写入无效
v.set(3);  //  写入无效
dbg!(v);  // 1

Arc和OnceLock的区别

好像区别不大哈, 不过 还是最好 OnceLock 用在全局static变量中, Arc用在多线程中

let mut v = OnceLock::new();
v.get_or_init(|| {
    Mutex::new(1)
});

for i in 0..19 {
    let a = v.get_mut().unwrap();
    *a.lock().unwrap() = i;
    println!("{:?}", v);
}

强制类型转换

如果两个结构体, 字段完全相同, 在safe的rust里是没有办法进行类型转换的

#[derive(Debug)]
struct Foo{
    a:i32
}
#[derive(Debug)]
struct Bar{
    a:i32
}

let f1 = Foo { a: 1 };
let mut b1 = Bar { a: 1 };
// b1 = f1;  // 这是禁止编译的   expected struct `main::Bar`, found struct `main::Foo`

// 尝试使用 unsafe 进行强制类型转换
let b2 :&Foo;
unsafe {
    b2 = &*(&b1 as *const Bar as *const Foo);  // 先把 引用数据b1转为 Bar类型的指针 再转为 Foo类型的指针, 如果这是后解引用, 由于转换后的类型的指针和 b1 指针指向的同一个位置的数据, 那么会获取b1的所有权, 所以应该reborrow, &* 让新的变量其位于指针之后
}
println!("{:?}", b2);  // Bar类型的 b1 转为了 Foo 类型

b1.a = 2;  // 修改了b1
println!("{:?}", b2);  // 同样影响了b2, 由此可见强制类型转换后的新类型的的指针, 数据实际指向的是b1,

强制转换 struct 为二进制数据

注意 &[T] 是slice的抽象内存表现 表示一块连续的内存里面存放的都是 T 类型数据

#[derive(Debug)]
struct Foo{
    a:i32
}
#[derive(Debug)]
struct Bar{
    a:i32
}

struct MyText{
    data: String
}

let mt = MyText { data: "123123hello".to_string() };
unsafe {
    // &mt as *const MyText  指 指向MyText  类型数据的指针    后面as *const &[u8] 把 指向MyText  类型数据的指针强制转换为 指向slice的指针
    let a = (&mt as *const MyText as *const &[u8]);
    println!("{:?}", a);
}

transmute 和 transmute_copy

mem::transmute<T, U> 接受一个 T 类型的值, 然后将它重新解析为类型 U, 唯一的限制是 T 和 U 必须有同样的大小

mem::transmute_copy<T, U> 很神奇地上面这更加不安全, 它从 &T 拷贝 size_of<U>个字节并将它们解析为 U, mem::transmute 仅有的类型大小的检查都不见了(因为拷贝类型前缀有可能是合法的), 只不过 U 的尺寸比 T 大会被视为一个未定义行为

let b2 = Bar { a: 2233 };
unsafe {
    let f2 = std::mem::transmute::<&Bar, &Foo>(&b2);
    println!("通过transmute创建的f2: {:?}", f2);
}