rust

153 阅读53分钟

认识基础结构

// 定义参数
fn main () {
    // main 函数是程序开始执行的地方
    println!("Hello, world!"); 
    // println! 宏用于打印文本到控制台
}
// rustc进行编译
// 编译后生成exe可执行文件和pdb调试文件

认识cargo

fn main() {
    println!("Hello, world!");
}

cargo new helloWorld,创建一个新项目

// cargo 构建和npm类似,它是一个命令行工具,用于管理Rust项目的构建和依赖项。

// cargo check 它的作用是检查项目是否能够成功编译,但不会生成可执行文件,执行速度比较快。相当于执行了rustc --check命令。

// cargo build 它的作用是编译项目,但不会运行生成的可执行文件。相当于执行了rustc命令。发布时要加--release参数。可以进行优化,生成更小的可执行文件。

// cargo run 它的作用是编译并运行由当前项目。如果没修改过Cargo.toml文件,那么cargo run会默认调用rustc来编译项目,然后运行编译好的可执行文件。





// cargo clean 它的作用是删除项目中的所有构建产物,包括生成的可执行文件和所有中间文件。相当于执行了rustc --clean命令。

// cargo doc 它的作用是生成项目的文档,但不会生成可执行文件。相当于执行了rustc --document-private-items命令。

// cargo test 它的作用是运行项目的测试,但不会生成可执行文件。相当于执行了rustc --test命令。

// cargo bench 它的作用是运行项目的基准测试,但不会生成可执行文件。相当于执行了rustc --bench命令。

// cargo fmt 它的作用是格式化项目的代码,但不会生成可执行文件。相当于执行了rustc --emit=fmt命令。

// cargo install 它的作用是安装一个Cargo包,但不会生成可执行文件。相当于执行了rustc --install命令。

// cargo uninstall 它的作用是卸载一个Cargo包,但不会生成可执行文件。相当于执行了rustc --uninstall命令。

// cargo update 它的作用是更新Cargo.lock文件中的依赖项版本,但不会生成可执行文件。相当于执行了rustc --update命令。

// cargo publish 它的作用是发布一个Cargo包,但不会生成可执行文件。相当于执行了rustc --publish命令。

// cargo login 它的作用是登录Cargo.io,但不会生成可执行文件。相当于执行了rustc --login命令。

// cargo search 它的作用是搜索Cargo.io上的包,但不会生成可执行文件。相当于执行了rustc --search命令。

一个猜数游戏

// 调用io标准库
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
    println!("这是一个猜数游戏!");

    // 生成一个随机数
    let secret_number = rand::thread_rng().gen_range(1..=100);

    print!("这个数为{}", secret_number);
    // 循环
    loop {
        println!("猜测一个数!");

        // rust默认所有变量都是不可变的
        // 如果不使用的变量,可以加下划线忽略,如let mut _foo = 1;
        let mut foo = 1;
        println!("{}", foo);
        // foo=3;这和js不太一样,rust不允许这样做

        // js对于原始数据类型(如 number、string、boolean、undefined、null、symbol 和 bigint),它们是存储在栈中的。
        // 当你执行 let foo = 123; 时,123 这个数值直接存储在栈中。当你之后执行 foo = 3;,这是直接在栈上改变了 foo 指向的值,即把 foo 这个变量的引用改为了新的数值 3。
        // 因此,对于原始数据类型,我们说是改变了栈值。
        // 而对于引用数据类型(如 object、array、function 等),情况就不同了。这些类型的值实际是存储在堆中,而变量中存储的是指向堆中对象的引用(这个引用本身存放在栈中)。
        // 当你改变这类值的内容时(比如修改数组的一个元素),你不是在改变引用本身的值(这个引用依然指向堆中的同一块内存),而是在改变堆中对象的状态。
        // 如果是js,在上面例子中,foo 最初指向一个原始数据类型(数字),所以改变 foo 的值实际上是直接在栈上替换了这个值,并没有涉及到堆。
        // 但是rust的栈值不能随便改动,所以rust不允许你直接改动原始数据类型的值。
        // rust如果想改变变量,需要加一个mut关键字,这样就相当于js的let了
        foo = 2;
        let bar = foo;
        println!("{}", bar);
        // 定义一个字符串
        // ::是rust表示关联函数,相当于js中的prototype,java中的static静态方法
        let mut guess = String::new();

        // 读取用户输入,将其放到 guess 变量的引用里,也就是赋值
        io::stdin()
            // 应为guess是随着用户的输入来修改,是可变的,所以要加mut,&表示引用,类似于react里的ref概念,一个是保持值不变,一个保持该值的栈地址不变
            // read_line会返回一个io::Result,io::Result是一个枚举类型,它有两个成员Ok和Err,分别表示成功和失败。类似js的Promise
            .read_line(&mut guess)
            // 如果出错,就抛错
            .expect("无法读取输入");

        // 把字符串转换成数字
        let guess: u32 = match guess.trim().parse(){
            Ok(num) => num,
            Err(_) => {
                println!("输入无效,请输入一个数字!");
                continue;
            }
        };
        // {}是rust的占位符,类似于js里的模板字符串
        println!("你猜测的数是:{}", guess);

        // cmp比较的作用
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("太小了!"),
            Ordering::Greater => println!("太大了!"),
            Ordering::Equal => {
                println!("恭喜你,猜对了!");
                break;
            }
        }
    }
}

通用的编程概念

// 调用io标准库
// use rand::Rng;
// use std::cmp::Ordering;
// use std::io;
fn main() {
//   变量和可变性
//   默认变量是不可变的,其实rust里的变量更应该叫一次性赋值量,不能再赋值,除非特殊情况
let x = 5;
// x=10;这样的话编译器会直接报错
println!("x值是{}", x);

let mut y = 6;

y=y*2; //这样就是可以的,let mut也就和js里的let一样

println!("y值是{}", y);


// shadowing 隐藏
// 使用相同的名字声明一个新的变量,这个新的变量会隐藏掉之前的变量
// 效果类似js的var
let a =5;
let a = a+1;//其实就是开个新地址,将上个值赋给新地址,再换上新名字
println!("a的值是{}", a);


// 使用场景,减少变量重命名
let spaces = "jakshds";
let spaces = spaces.len();
println!("spaces的值从字符串变成了字符串长度:{}", spaces);





// 常量:所有的字母都是大写的,用下划线来连接
// 常量在绑定值之后不能再改变,也就是说let后面是可以改成let mut的,但是const不行
// 类型必须指定,而且必须指定初始值
// 常量只可以绑定到常量表达式,无法绑定到函数调用的结果或只有运行时才能确定的值
const Z_CONST:i32 = 100;
println!("z的值是{}", Z_CONST);
}

基本数据类型

// 数据类型
// rust是静态类型语言,编译时确定类型,所以变量必须声明类型
// rust有自动类型推断,但是类型推断只适用于let
fn main() {
    // 标量类型,也就是基础数据类型,有四大类
    // 整数类型
    // 浮点类型
    // 布尔类型
    // 字符类型

    // 整数类型
    // 无符号类型
    // u32占据32位空间,范围为0~2^32-1
    let u32_var: u32 = 10;
    // 有符号类型
    // i32占据32位空间,范围为-2^31~2^31-1,i32是默认类型
    let i32_var: i32 = 10;

    // 整形范围,后面的数字表示它占的位数,size表示其位数随系统架构
    // u8,u16,u32,u64,u128,usize
    // i8,i16,i32,i64,i128,isize

    // 整形字面量,数值里可以加下划线,方便阅读,rust特有
    // 十进制
    let decimal = 98_222;
    // 十六进制
    let hex = 0xff;
    // 八进制
    let oct = 0o77;
    // 二进制
    let binary = 0b1111_0000;

    // 整数溢出
    // 在开发中,如果遇到整数溢出,rust会报错,不会自动溢出
    // 在发布版中,rust会自动溢出,形成整数环流,参考时钟

    // 浮点类型
    // f32,f64,f64是默认类型
    let f32_var: f32 = 10.0;
    let f64_var: f64 = 10.0;

    // 数值计算
    let sum = 5 + 10;
    let difference = 95.5 - 4.3;
    let product = 4 * 30;
    let quotient = 56.7 / 32.2;
    let remainder = 43 % 5;

    // 布尔类型
    let t = true;
    let f: bool = false;


    // 字符类型用单引号表示,占用四个字节,包括ASCII和Unicode,空白字符,emoji表情
    let c = 'z';
    let z: char = 'ℤ';

}

复合数据类型

fn main() {
    // 复合数据类型,也就是引用数据类型
    // 可以将多个值组合成一个复合值
    // 包括元组,数组,结构体,枚举,向量,哈希映射

    // 元组tuple
    // 元组中的元素可以是不同的类型
    // tuple的长度是固定的,一旦声明,就无法修改
    let tup: (i32, f64, u8) = (500, 6.4, 1);
    // 对元组结构赋值,就把(),理解成js的{}就好
    let (x, y, z) = tup;
    println!("x,y,z is: {} {} {}", x, y, z);

    // 访问tuple的元素
    let five_hundred = tup.0;
    let six_point_four = tup.1;
    let one = tup.2;

    println!(
        "five_hundred, six_point_four, one is: {} {} {}",
        five_hundred, six_point_four, one
    );
}

    // 数组,和tuple类似,但是数组中的元素类型必须相同
    // 数组的长度也是固定的,类型声明时,第一个是数组元素类型,第二个是数组长度

    // 数组的声明,有两种
    let a: [i32; 5] = [1, 2, 3, 4, 5];
    // 如果数组中的元素类型相同,可以省略类型声明,类似js的full
    let b = [3; 5];

    // 访问数组的元素
    let first = a[0];
    let second = a[1];
    println!("first, second is: {} {}", first, second);

    // 如果索引超出,编译时是不会报错的,但是运行时会报错

    // 后面还会学一个可变长度的数组,叫做向量vector,它的长度是可变的,更灵活
    // 如果不确定使用哪个,建议使用向量vector

函数

// 函数
// main 函数是 Rust 程序的入口点
fn main() {
    // 函数是以关键字 fn 开头,后跟函数名和一对圆括号
    // 函数体被一对花括号包围
    // 命名规范是小写,用下划线分割

    // 函数的参数
    // parameter 形参
    // arguments 实参
    // 函数参数必须注明类型

    fn another_function(name: String, age: i32) {
        println!("你的名字是:{}, 年龄是:{}", name, age);
    }
    // 调用函数
    another_function(String::from("张三"), 18);


    // 语句是来执行的
    // 表达式是有返回值的,哪怕是返回空的
    // Rust 是一门注重表达式的语言、
    // 函数默认就是表达式,函数体中最后一个表达式会作为函数的返回值
    // 语句不能赋值给变量,表达式可以,想想也能明白,都没有返回值哪来的赋值
    let x = {
        let y = 5;
        y + 1
    };
    println!("x的值是:{}", x);
    //会返回6
    // 这段代码相当于js的立即执行函数和不写return的箭头函数结合,注意y+1表示返回时,后面不能有分号的


    // 函数的返回值,把rust的函数当js的不写return的箭头函数理解最好
    // 函数返回值使用 -> 符号后跟返回值的类型
    // 函数体中最后一个表达式会作为函数的返回值
    // 如果函数体中最后一个表达式没有分号,那么它的值就是函数的返回值
    // 如果函数体中最后一个表达式有分号,那么函数的返回值就是 ()
    // 如果想提前返回就使用return关键字
    fn add(x: i32, y: i32) -> i32 {
        x + y
    }

    println!("add函数的返回值是:{}", add(1, 2));
}

流程控制

// 控制流,流程语句
fn main() {
    if_code();
    match_code();
    if_condition();


    loop_code();
    while_code();
    for_code();
    range_code();
    
    break_code();
    continue_code()
}

fn if_code() {
    // if表达式,和js的区别在于,它不用给判断条件加小括号
    let number = 6;
    if number % 3 == 0 {
        println!("{} 是3的倍数", number);
    } else if number % 2 == 0 {
        println!("{} 是2的倍数", number);
    } else {
        println!("{} 既不是3的倍数也不是2的倍数", number);
    }
}

fn match_code() {
    // match表达式,类似于js中的switch
    let number = 4;
    match number {
        1 => println!("One!"),
        2 => println!("Two!"),
        3 => println!("Three!"),
        4 => println!("Four!"),
        5 => println!("Five!"),
        _ => println!("Something else!"),
    }
}

fn if_condition() {
    // if条件,rust的if是一个表达式,可以赋值给别人,类似于js中的三元运算符
    let age = 20;
    let number = if age < 18 {
        1
    } else if age < 24 {
        2
    } else {
        3
    };
    match number {
        1 => println!("未成年!"),
        2 => println!("青年!"),
        _ => println!("成年!"),
    }
}

// 循环
fn loop_code() {
    // loop循环,类似于js中的while(true)
    let mut counter = 0;
    loop {
        println!("loop!{}", counter);
        counter += 1;
        if counter == 5 {
            break;
        }
    }
}
fn while_code() {
    // while循环,类似于js中的while
    let mut number = 3;
    while number != 0 {
        println!("while{}!", number);
        number -= 1;
    }
}
fn for_code() {
    // for循环,类似于js中的for
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("for {}!", element);
    }
}

// range循环,类似于js中的for(let i=0;i<10;i++)
fn range_code() {
    for number in (1..4).rev() {
        // 1<=number<4
        println!("range {}!", number);
    }
}

// break和continue,类似于js中的break和continue
fn break_code() {
    let a = [1, 2, 3, 4, 5];
    for element in a.iter() {
        if element == &3 {
            break;
            // 表示运行到这结束123
        }
        println!("break {}!", element);
    }
}
fn continue_code() {
    let a = [1, 2, 3, 4, 5];
    for element in a.iter() {
        if element == &3 {
            continue;
            // 表示忽略这次循环1245
        }
        println!("continue {}!", element);
    }
}

所有权

// 所有权
// 内存管理的三种方式
// 手动释放 如:从 c,c++
// GC释放 如:java,js
// Rust的所有权系统通过所有权规则来防止内存泄漏

// stsck和 heap
// stack: 存储编译时已知大小的变量,比如i32,f64,char,bool,以及所有其他基本数据类型
// stack的变量在离开作用域时会被自动丢弃,
// stack不用分配内存,因为大小已知,挨个放就好
// stack压栈,后进先出,每次找最上面的值就好,就像弹夹,每次出去都是最上面的,每次都找所以速度很快

// heap: 存储在编译时大小未知或大小可能变化的数据,比如:String,Vec<T>,
// heap的变量需要手动释放,否则会造成内存泄漏,也就是说只有堆上的数据需要手动释放
// heap是在使用时找到足够大的空间,标记再用,并返回地址值来引用
// heap是要分配地址和寻址的,所以速度慢,但是可以存储大小未知或大小可能变化的数据

// 1. Rust 中的每一个值都有一个被称为其所有者(owner)的变量
// 2. 值在任一时刻只能有一个所有者
// 3. 当所有者(变量)离开作用域,值会被丢弃
// 4. 这是几乎是零开销,不会影响编译时的性能

// 作用域的例子(也就是常说的生命周期)

fn main() {
    string_code();
    string_heap();
}

// 简单通用的例子
fn string_code() {
    // s不可用,还没声明,也就是还没出生
    let s = "hello";
    // s 声明和赋值,进入作用域
    println!("{}", s);
    // s 在这里可用,可以进行各种操作
}
// // s 离开作用域,被丢弃,内存被释放,s不可用,也就是死亡了

// 复杂点的例子
fn string_heap() {
    // 基础数据类型是存在栈上面,栈弹出,这数据就释放了
    // String是放在堆上面的引用数据类型,就像是字符数组的样式

    // 字符串分字符串字面值和字符串对象
    // 字符串字面值是不可变的,就是程序里直接写出来的字符串
    // 字符串对象是可变的,是通过String::from()方法创建的,也就是堆上的数据

    let mut s = String::from("hello"); // s 在这里可用,可以进行各种操作
    s.push_str(" world"); // s 在这里使用push,在原字符串后面添加
    println!("{}", s); // 输出
}

// 内存分配
// 字符串字面值是存储在栈上的,因为大小已知,所以可以直接分配,所以也不能乱变,就像是排队,你不能突然让你朋友站你后面插队
// 字符串对象是个变量,因为大小未知,是存储在堆上的操作,就像去饭店吃饭,多个人就在空位再找一个位子就好

// 变量和数据的交互方式就是Move(移动)
// 当变量离开作用域,Rust会自动调用drop函数,释放内存,就像离开饭店,你吃完饭,服务员会自动帮你收拾桌子

fn code() {
    let a = 5;
    let b = a;
    // a 在这里仍然可用,因为栈中 a 被拷贝给了栈中 b,a 并没有被“移动”或“丢弃”
    // 对于基本数据类型(如整数、浮点数、布尔值等),Rust 编译器会进行值拷贝。
    // 这意味着当你将 a 的值赋给 b 时,b 会获得 a 值的完全独立的副本。因此,a 和 b 在内存中各自存储了相同的值,但它们并不共享这个值的内存位置。

    let s1 = String::from("hello");
    let s2 = s1;
    // s1 在这里不再可用,因为 s1 的值被“移动”给了 s2
    // print!("{} {}", s1, s2);  编译器会报错,因为 s1 在这里已经被丢弃
    // 一个字符串由三个部分组成,一个指向堆地址的指针,一个长度,一个容量,当 s1 被移动给 s2 时,s1 的指针、长度和容量都被移动给了 s2,s1 就被丢弃了,所以 s1 不再可用

    // 为啥要用移动,而不是引用
    // js举例
    // let obj1 = {a: 1, b: 2};
    // let obj2 = obj;
    // 为了举例的假设情况下。要是释放内存,会先释放obj2,再释放obj1,因为obj2是obj1的引用
    // 但是obj2释放了,obj1堆地址的指针还在指向这段位置,如果这时候这里被写入了值,就会二次释放造成野指针问题
    // 释放obj1,就会把别的数据给释放了

    // 所以rust直接就让 obj1不可用,这样这堆的地址值只会被一个变量所拥有,就不会造成野指针问题
    // 类似js
    // let obj1 = {a: 1, b: 2};
    // let obj2 = obj;
    // let obj1=null;
    // 这样obj1就提前释放了,上面也就没有野指针了

    // 上面说的Move,会让人们想到浅拷贝(shallow copy),深拷贝(deep copy)
    // 浅拷贝就像是一个房子,夫妻两有两个不一样的钥匙,但是都是用一个房子里的东西
    // 深拷贝就像是两个一摸一样的房子,夫妻两有两个不一样的钥匙,各住各的
    // 所以浅拷贝和深拷贝的区别就在于,浅拷贝是共享内存,深拷贝是独立内存
    // 但是现在还是房子,夫妻就剩一把钥匙,谁用给谁,另一个人就进不了门,这就是rust的Move

    // rust其实也是有clone的
    let s3 =s2.clone();
    // 在这时候就类似js的深拷贝了,深拷贝就像是两个一摸一样的房子,夫妻两有两个不一样的钥匙,各住各的
    // 非常消耗性能的,毕竟要购买一个新的一摸一样的房子
}

// Copy trait
// 基本数据类型和能深度确定大小的引用数据类型都是Copy trait的
// 整型,布尔,数值,字符
// 基础数据类型组成的引用数据类型,如
// 纯数组,上面四个基础类型组成的元组

// 不能Copy trait的,典型的包含String对象类型的

函数和所有权

fn main (){
    // 所有权和函数
    // 在语义上把值传递给函数和把值给变量是相似的,因为这两者都转移了所有权。
    // 把值传给函数要不发生移动,要不发生复制
    let s1 = String::from("hello");
    take_ownership(s1); // s1 被移动到函数里
    // println!("{}", s1); //s1 在这里不再有效,它手里的值已经给了函数,就理解成它的房间钥匙已经给了函数的形参


    let x = 5; // x 进入作用域
    make_copy(x); // x是一个基本值,所以是被复制到函数里
    println!("{}", x); // x 在这里依然有效

}
fn take_ownership(s: String) { // s 进入作用域
    println!("{}", s);
}
fn make_copy(x: i32) { // x 进入作用域
    println!("{}", x);
}

参数的返回和数据的引用

fn main() {
    code1();
    code2();
    code3();
    code4();
    code5();
    code6();
    let mut s = String::from("hello world");

    let world_index = code8(&mut s);
    println!("{}", world_index);
}

fn code1() {
    let _s1 = gives_ownership();

    // 无参函数的返回
    // 调用 gives_ownership 函数后,some_string的值的所有权被转移给了s1,所以s1拥有字符串 "hello" 的所有权
    fn gives_ownership() -> String {
        let some_string = String::from("hello");
        some_string
    }
}

fn code2() {
    let s2 = String::from("hello");
    let _s3 = takes_and_gives_back(s2);

    // 有参函数的返回
    // takes_and_gives_back函数接收一个String类型的参数,并返回一个String类型,即s2将值的所有权转移给了函数的形参,函数再返回给s3
    fn takes_and_gives_back(a_string: String) -> String {
        a_string
    }
}

fn code3() {
    let s4 = String::from("返回所有权");
    let (s4, len) = use_value_in_function(s4);
    println!("s4 = {}, len = {}", s4, len);
    // 函数使用变量,并返回所有权
    fn use_value_in_function(some_string: String) -> (String, usize) {
        let lenght: usize = some_string.len();
        // 原理就是把值再返回回去,有一个同名的变量再接受
        (some_string, lenght)
    }
}

// 变量的引用
// Rust中的引用允许你使用值而不获取其所有权,Rust中的引用有三种:不可变引用、可变引用、悬垂引用
// 不可变引用:引用一个值,但不允许修改它
// 可变引用:引用一个值,并允许修改它
// 悬垂引用:一个已经被释放的变量的引用,也就是野指针,大bug!

// 还是夫妻住房问题,还是只有一把钥匙,现在两个都想能进门,但是妻子没钥匙,所以妻子直接喊丈夫回来开门,这就是引用
// 所以这样妻子就能进入房间,但是它不拥有这把钥匙,也就是说,哪怕妻子离婚出走,丈夫都能打开门,这就是引用
// 引用者离开作用域时,只会清除本身的引用,不会清除引用的值

fn code4() {
    let s5 = String::from("借用的字符串");
    let len = use_value_in_function1(&s5);
    println!("s5={},len = {}", s5, len);
    // 引用就在前加上一个&符号,例如let s1: &String = &s2;,这样s1就引用了s2
    // 借用就是函数参数对变量的引用,两个几乎是一个东西,使用了&符号,其实就很贴近平时的编程习惯了
    fn use_value_in_function1(some_string: &String) -> usize {
        some_string.len()
    }
}

// 上面是不可变引用,下面是可变引用
fn code5() {
    let mut s6 = String::from("可变引用");
    use_value_in_function2(&mut s6);
    println!("s6 = {}", s6);
    // 可变引用:引用一个值,并允许修改它
    // 其实和可变变量一个道理,一个房子不让装修,你就不能动。如果让装修,你就可以动
    fn use_value_in_function2(some_string: &mut String) {
        some_string.push_str(", world");
    }
}

// 为了避免数据竞争,Rust在编译时对可变引用进行了严格的限制,即在同一作用域内,只能有一个可变引用,或者多个不可变引用
fn code6() {
    let mut s7 = String::from("可变引用");
    let _r1 = &mut s7;
    let _r2 = &mut s7;
    // println!("r1 = {}, r2 = {}", r1, r2);
    // 不可同时存在,否则报错,如果只是声明是没事的,只要一进行读写操作就会报错,就像上面这个打印,就会报错
    // 就好比房子请施工队装修,只能找一个施工队负责,不然两队各有各的风格和进度,就会一团糟
}

// 一定要注意是同一个作用域不可以!!!!!不同作用域就可以,因为不同作用域互不影响
fn code7() {
    let mut s8 = String::from("可变引用");
    {
        let r1 = &mut s8;
        // 不能直接打印r1,但我们可以打印它所引用的字符串
        println!("r1引用的字符串 = {}", *r1);
        // 或者如果你只是想看看是否能进入这个作用域而不做任何操作,上面的println就足够了
    }
    // r1的作用域已结束,现在我们可以安全地再次借用s8
    let r2 = &mut s8;
    // 同样地,我们不能直接打印r2,但可以打印它所引用的内容
    println!("r2引用的字符串 = {}", *r2);
}

切片和字符串的切片的应用

fn main() {
    let mut s = String::from("hello world");

    let hello=&s[0..5];
    // 表示0<=index<5的切片,其实像这种0开始的,可以省略前面的0
    // let hello=&s[..5];与上面一样的效果
    let world = &s[6..11];
    // 如果是到最后其实就是字符串的长度,可以写成
    // let world = &s[6..s.len()];
    // let world = &s[6..];

    // 由上可得,如果我想要全部长度的字符串
    // let world = &s[0..s.len()];
    // let world = &s[..];

    let world_index = code1(&mut s);
    println!("{}", world_index);

    let world_index_2= code2(&s);
    // s.clear();
    println!("{}", world_index_2);
}


// 切片
// 切片是对集合中一部分的引用,而不是整个集合的引用,切片是引用,所以它没有所有权
// 没有使用切片,返回的索引和原数据没有任何关联,哪怕字符串已经被清空,返回的数据依旧是在
// 就导致期间如果字符串发生变动,再利用返回的索引就会去查,就会出错
fn code1(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    s.len()
}

// 下面就不返回字符串的索引,而是返回一个切片
// 这样返回的是对string的切片的引用,所以在切片作用域内,原字符串不能被清空和改变
fn code2(s: &String) -> &str { 
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
 }

//  其实切片最经常看到的例子就是字符串字面值
//  字符串字面量的类型就是&str,它就是一种切片的表现,它指向了程序可执行文件中的某个位置
//  因为在切片引用生命周期结束之前,源数据不能改变
//  所以字符串字面值是不可变的

// 之前的函数中的字符串参数都是用的&String,其实也可以用&str,这样就可以即接收&String,也可以接收&str
// fn code3(s: &str) -> &str { s },如果参数是&str,那么函数的参数就只能是字符串字面值了,如果这时候传入&String,那就是会对其进行切片
//  所以&str是&String的特化,&String是&str的泛化;

struct

fn main() {
    struct Person {
        name: String,
        age: u8,
        is_male: bool,
    }
    // 结构体
    // 就是类似一个ts的type,java的抽象类,多种数据的集合包
    // 其中一开始就只有c的结构体,c++就是将其抽象成了类
    // 正常情况下,结构体定义几个属性,那创建实例就要有几个属性
    // 读实例的属性,就是一个person.name这样的小点
    // rust的结构体有个特点就是如果其中一个属性是可变的,那所有的都是可变的,如果都不变,那都是不可变的
    
    let person = Person {
        name: String::from("张三"),
        age: 18,
        is_male: true,
    };
    let sex = if person.is_male { "男" } else { "女" };
    println!(
        "这个人名字叫{} , 年龄是{} , 性别是{}",
        person.name, person.age, sex
    );
    // 和js一样,属性名如果和值是一样的,那就可以简写
    // 可以作为参数传递给函数,也可以作为返回值
    
    // 结构体的更新,先高呼js牛逼
    
    let _person1 = Person {
        name: String::from("李四"),
        age: person.age,
        is_male: person.is_male,
    };
    
    // 更新语法,其实就是js展开符
    let _person2 = Person {
        name: String::from("李四"),
        ..person
    };
    
    // tuple struct
    
    // 结构体和元组类似,但是元组没有名字,结构体有名字
    // 结构体可以包含多个不同类型的值,元组只能包含相同类型的值,一个类似对象,一个类似数组
    // 结构体和元组都可以作为函数的参数和返回值
    // 但是rust的struct可以和tuple结合,变成一个有名字的数组
    // 这里的名字是指定数组中每个元素的名称,对象是键值对,有键有值;数组只有值
    struct Color(i32, i32, i32);
    println!(
        "{} {} {}",
        Color(255, 0, 0).0,
        Color(255, 0, 0).1,
        Color(255, 0, 0).2
    );
    
    let _color = Color(255, 0, 0);
    
    // 空结构
    // 当我们需要定义一个结构体,但是暂时不需要任何字段时,可以使用空结构体
    struct Empty;
    let _empty = Empty {};
    
    //  struct数据的所有权,如Person,name是String,所以name的所有权在person中,其他属性又是基本数据类型
    // 所以该结构体的实例拥有其所有属性的所有权
    // 只要struct实例还在,那么其所有字段的所有权也会一直存在
    
    // struct里面也可以放引用数据类型,但是在rust的这门语言中,当然也要考虑该结构体实例引用的引用数据的声明周期
    
    // 一个结构体来表示长方形,来计算长方形的面积
    #[derive(Debug)]
    struct Rectangle {
        width: u32,
        height: u32,
    }
    let rectangle = Rectangle {
        width: 30,
        height: 50,
    };
    fn rectangle_area(rectangle: &Rectangle) -> u32 {
        rectangle.width * rectangle.height
    }
    println!("长方形面积:{}", rectangle_area(&rectangle));
    println!("长方形面积的宽:{}", rectangle.width);
    // 如果我们要看一个完整的结构体,并不像js那么容易
    // 只可以使用debug格式打印,在结构体上面加#[derive(Debug)],其实就是ts的注解
    // 不能使用{}号了,要使用{:?},表示结构未知。如果想要更好地格式展示数据,可以用{:#?}
    println!("长方形数据:{:?}", rectangle);
}

struct方法

// 函数和方法的区别
// 函数:定义在模块或包中,可以访问模块或包中的变量和函数
// 方法:定义在结构体或枚举中,可以访问结构体或枚举中的变量和函数
fn main() {
    struct Person {
        name: String,
        age: u8,
        is_male: bool,
    }
    // 方法,和别的语言不同的就是,结构体里面只能存属性,不能存方法
    // 要想存方法要用impl(扩展)关键字,第一个参数一定是&self,这个就是js的this
    impl Person {
        fn get_age(&self) -> u8 {
            self.age
        }
    }
    let p = Person {
        name: "zhangsan".to_string(),
        age: 18,
        is_male: true,
    };
    println!("age:{}", p.get_age());

    // 关联函数,就是静态方法
    // 使用是通过::来调用,他是不用要实例化就可以调用的
    // 下面通过静态方法,创建一个构造函数
    impl Person {
        fn new(name: String, age: u8, is_male: bool) -> Self {
            Person { name, age, is_male }
        }
    }
        
    let p = Person::new("zhangsan".to_string(), 18, true);
    println!("age:{}", p.get_age());
}

枚举和模式匹配

代码有点问题,有几个警告没能解决,等以后理解透了,再来看看

// 枚举
fn main() {
    main2();
    main3();
    main4();
    main5()
}
fn main2() {
    // 枚举的简单使用
    // 声明枚举类型,类似类的静态属性
    enum IPAddrKind {
        V4,
        V6,
    }

    // 定义枚举变量
    let four = IPAddrKind::V4;
    let six = IPAddrKind::V6;

    // 匹配枚举类型
    fn route(ip_type: IPAddrKind) {
        match ip_type {
            IPAddrKind::V4 => println!("ip_type: IPv4"),
            IPAddrKind::V6 => println!("ip_type: IPv6"),
        }
    }
    route(four);
    route(six);
}
fn main3() {
    #[derive(Debug)]
    enum IPAddrKind {
        V4,
        V6,
    }
    // 枚举作为结构体的字段使用,字段就是只有声明,没有定义的的属性
    #[derive(Debug)]
    struct IPAddr {
        kind: IPAddrKind,
        address: String,
    }
    let home: IPAddr = IPAddr {
        kind: IPAddrKind::V4,
        address: String::from("127.0.0.1"),
    };

    let loopback: IPAddr = IPAddr {
        kind: IPAddrKind::V6,
        address: String::from("::1"),
    };
    println!("Home: {:?}", home);
    println!("Loopback: {:?}", loopback);
}
fn main4() {
    // 每个枚举可以有不同的类型
    #[derive(Debug)]
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    // 注意:IPv4 地址的每个部分都需要是 u8 类型的字面量或变量
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

    // 打印这两个 IP 地址
    println!("Home: {:?}", home);
    println!("Loopback: {:?}", loopback);
}
fn main5() {
    enum Message {
        Quit,
        Move { x: i32, y: i32 }, //Move是个匿名的结构体,包含x和y两个属性
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    impl Message {
        fn call(&self) {
            // 在这里实现方法体
        }
    }
    let q = Message::Quit;
    let m= Message::Move { x: 10, y: 20 };
    let w = Message::Write(String::from("hello"));
    let c = Message::ChangeColor(0, 160, 255);
    m.call();
}

option枚举

// 枚举
fn main() {
    // option枚举定义在标准库中
    // 在prelude(预导入模块)中,所以可以直接使用
    // Option枚举有Some和None两个变体,表示一个值可能存在或者不存在
    // 在其他语言中,这种类型通常被称为“可能为空”或者“可能为null”的类型
    // 因为null值并不安全,就好比一个对象,假设它的值为null,那么调用它的方法就会报错
    // 所以Rust语言没有null值,但是提供了Option枚举来处理可能为空的情况,表示一个因为为某种原因无效或缺失的值

    // 本质上就是一个枚举,有两个变体,Some和None
    // enum Option<T> {
    //     Some(T),
    //     None,
    // }
    main2();
}
fn main2() {
    // Option枚举可以用于任何类型,包括结构体、元组、枚举等
    // Option枚举有两个状态,Some和None,分别表示一个值存在或者不存在
    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number: Option<i32> = None;
    // 这里的absent_number是一个Option<i32>类型的变量,它的值是None,表示这个变量没有值
    // 这就相当于一个空值,相当于ts的<number|null>
    // 但是它比null更安全,因为Option枚举可以明确地表示一个值可能存在或者不存在

    println!("some_number: {:?}", some_number);
    println!("some_string: {:?}", some_string);
    println!("absent_number: {:?}", absent_number)
}

枚举和匹配

// 枚举
fn main() {
    // 模式匹配
    let coin = Coin::Penny;
    value_in_cents(coin);

    // 枚举的值中还能传入参数
    let coin2 = Coin::Quarter(UsState::Alaska);
    value_in_cents(coin2);


    // 匹配可能为空的值
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);


    // 只匹配某些值,剩余的值用 _ 来匹配
    let some_u8_value = 0u8;
    match_numbers(some_u8_value);

}

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
}

// 枚举的值中还能传入参数
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}
fn value_in_cents(coin: Coin) {
    match coin {
        Coin::Penny => println!("1 cent"),
        Coin::Nickel => println!("5 cent"),
        Coin::Dime => println!("10 cent"),
        Coin::Quarter (state) => {
            println!("25 cent from {:?}", state)
        } 
    }
}


fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        // 就是说如果x是Some,那么就返回Some(i + 1),如果是null,就返回null
        // match必须覆盖所有可能的值,否则编译器会报错
        Some(i) => {
            println!("Some(i) = {:?}", i);
            Some(i + 1)}
        None => {
            println!("None");
            None
        },
    }
}

fn match_numbers(x:u8) {
    match x {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        7 => println!("seven"),
        _ => println!("other"),
    }
    
}

组织结构

fn main() {
    // rust组织结构
    // 1.顶层包 Package 也就是js的中常说的项目,就好比一个一个学校
    // 2.以体系树区分的单元 Crate ,也就是js中的包,就好比学校里的各个学院
    // 3.单元中以功能模块 Module ,也就是js的模块,就好比学院里的各个专业

    // Crate有两种类型:二进制binary crate和库library crate

    // 一个Package里有:
    // Cargo.toml文件:定义了包的元数据,比如包的名称、版本、作者、依赖项等,以及如何构建和打包包的指令,描述了如何构建这个包
    // Crate Root: 一个crate的根模块,它定义了crate的公有API
    // rust编译器开始编译的入口,也就是main.rs
    // 只能包含零个或一个library crate,因为作为库开发时,lib.rs文件是library crate是包的入口,且只能有一个
    // 能包含多个binary crate,因为binary crate是可执行文件,是是包的依赖包,在src/bin下面
    // module对代码进行分组,控制作用域,方便代码的复用,增加可读性,隐藏实现细节,提高安全性。关键字:mod

    // 创建了模块,如果想使用,就要通过路径来访问,路径分为绝对路径和相对路径
    // 绝对路径从crate root开始,相对路径从当前模块开始
    // 相对路径从当前模块开始,使用关键字self、super来相对定位
}

路径

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
        pub fn seat_at_table() {}
    }

    pub mod serving {
        pub fn take_order() {}

        // 如果这个函数前面没有 pub,那么它就是私有的private,只能在当前模块中使用
        // 私有只能在当前模块中使用,父模块无法访问,但是子模块可以访问
        // 如果两个模块都在一个文件的根级上,那么这两个模块是兄弟模块,兄弟模块之间可以互相访问
        // 就像front_of_house和eat_at_restaurant,front_of_house虽然是私有的,但是eat_at_restaurant可以访问

        fn serve_order() {
            // super关键字,表示父级,::表示子级
            super::hosting::add_to_waitlist()
        }
    }
}

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::front_of_house::hosting::add_to_waitlist();
    // 相对路径
    front_of_house::hosting::seat_at_table();
}

模块

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}

        pub fn seat_at_table() {}

        pub fn serve_order() {}
    }
}

// 通过use关键字来导入模块,
use crate::front_of_house::hosting;
// 使用as关键字来给导入的模块起别名
use front_of_house::hosting::seat_at_table as seat;
// 嵌套导入
use std::{
    cmp::Ordering,
    io::{
        self,//就表示导入std::io自身
        Write,
    }
};
// 全部引入,但是不推荐使用,一般在测试时使用
use std::collections::*;


pub fn eat_at_restaurant() {
    // 绝对路径
    crate::front_of_house::hosting::add_to_waitlist();
    // 相对路径
    front_of_house::hosting::seat_at_table();
    // 使用use关键字来导入模块
    hosting::add_to_waitlist();

    seat()
}




// 将模块放在文件中
// main.rs
mod front_of_house;

// front_of_house.rs
pub mod hosting {
   pub fn add_to_waitlist() {}
   pub fn seat_at_table() {}
   pub fn serve_order() {}
   }





对比js的模块系统

//js的模块
module.exports = {
    add_to_waitlist: function() {}
}
//rust的模块
pub mod hosting {
    pub fn add_to_waitlist() {}
}


// 在js中,使用import导入模块
import { add_to_waitlist } from './hosting.js'
// 在rust中,使用use导入模块
use crate::hosting::add_to_waitlist;

// js的暴露引用
export waitlist  from './hosting.js'
// rust的暴露引用
pub use crate::front_of_house::hosting;

Vector集合

fn main() {
    // 集合之Vector

    // 用new创建一个空的Vector
    let _v: Vec<i32> = Vec::new();
    // 用vec!宏创建一个包含初始值的Vector
    let mut v1 = vec![1, 2, 3];
    // 向Vector中添加元素
    v1.push(1);
    // 删除Vector中的元素
    v1.remove(0);

    // 访问Vector中的元素的方法
    let third = &v1[2];
    println!("The third element is {}", third);
    match v1.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
    }
    // 遍历Vector中的元素
    for i in &mut v1 {
        *i += 50; 
        // 修改Vector中的元素
    }
    for i in v1 {
        println!("{}", i);
    }
}



fn main2() {
    // Vector是可变的,所以需要mut
    let mut v1 = vec![1, 2, 3];
    // 有定义了不可变的Vector切片
    let first = &v1[0];
    // 修改Vector中的元素,会报错,因为first是Vector的不可变引用
    v1.push(4);
    println!("The first element is: {}", first);
}

fn main3() {
    // vector和emun的结合就能使用枚举来存储多种类型的数据
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

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

String

fn main() {
    // 字符串
    // rust中字符串是UTF-8编码的,只有一个字符串类型,切片str或&str
    // 字符串字面量,是存储在二进制文件中,字符串字面量是不可变的

    // 而String类型,则是来自于标准库的,是可变的,可增长,可修改的,可拥有的,也是UTF-8编码的
    // 在其他库中,有着更多的字符串类型,比如:OsString,OsStr,PathBuf,Path等

    // String本质上是bytes的集合,所以集合Vec<T>的很多方法都是可以用的,String可以看做是Vec<u8>的封装

    // 定义
    let mut _s = String::new();
    // 创建的两种方法
    // 字面量创建,from函数创建
    let mut s1 = String::from("hello");

    // to_string方法
    let data = "world";
    let s2 = data.to_string();

    let s3 = "hello".to_string();

    // 字符串拼接
    s1.push_str("World");
    println!("{}", s1);
    // s1.push('!'); // push方法只能接受一个字符
    s1.push_str("!");
    println!("{}", s1);
    // 加号拼接,前面是String类型,后面是&str类型,后面是一个字符串的引用
    // 使用加号拼接,会创建一个新的String,并复制数据,所以性能较低。所以s1的值会被丢弃
    // 类似fn add(self, s: &str) -> String
    let s4 = s1 + &s2;
    // format!宏,性能比加号拼接好,因为format!宏会创建一个String,并直接将数据写入,不会丢弃s1
    // format!宏的用法和println!宏类似,不过它不会打印到控制台,而是返回一个String,类似模板字符串
    // 它不会获得所用参数的所有权,所以s3和s2的值不会被丢弃,后面还能继续使用
    let s5 = format!("{}-{}", s3, s2);

    // String不能直接使用索引访问,因为字符串是UTF-8编码的,一个字符可能占用多个字节
    // 中文一个字符占用3个字节,英文一个字符占用1个字节
    let len = s2.len();
    println!("len: {}", len);

    // rust看待字符:字节,标量值,字形簇(字形)
    // 字节,每个字节是个二进制的数值
    for b in s4.bytes() {
        println!("{}", b);
    }
    // 标量,二进制数值表示的字符
    for c in s4.chars() {
        println!("{}", c);
    }
    // 字形簇,标准库没这方法


    // 字符串的切片,如果一个字母占用多个字节,切片恰好切在它的中间,那就会在编译时报错
    let s6=&s5[0..5];
    println!("{}", s6);
}

HashMap

use std::collections::HashMap;
fn main() {
//  HashMap<K,V>:键值对集合,通过键来查找数据,而不是索引

// 创建一个空的HashMap,空的HashMap会报错,因为HashMap是空的,所以无法确定它的类型
// 解决办法是使用类型标注,或者添加数据,这样HashMap就会知道它的类型
    let mut scores:HashMap<String, i32> = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    // HashMap用的比较少,所以他不在标准库中,而是放在了collections模块中
    // HashMap的数据是保存在堆上的,所以可以存储任何类型的数据,但是是同构的,即所有的键和值必须是相同类型,相当于是变体的数组

    // 另一个创建HashMap的方法collect,collect方法可以将迭代器中的元素收集到HashMap中
    let teams = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];
    let mut scores1:HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
    // zip方法可以将两个迭代器组合成一个迭代器,每次迭代时返回一个元组,元组的第一个元素来自第一个迭代器,第二个元素来自第二个迭代器
    // collect方法能返回很多种类型的数据结构,所以我们要指明想要的数据类型

    // HashMap的所有权
    // 对于实现了Copy trait的类型,它们会被复制到HashMap中,对于没有实现Copy trait的类型,如String,它们的所有权会被转移
    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");
    let mut scores2 = HashMap::new();
    scores2.insert(field_name, field_value);

    // 如果把值的引用插入到HashMap中,值本身就不会移动


    // 访问HashMap中的值
    let team_name = String::from("Blue");
    let score = scores.get(&team_name);
    // get方法返回一个Option,因为可能不存在对应的键,所以需要使用unwrap方法来获取值,或者使用if let来处理Option
    match score {
        Some(s) => println!("The score is {}", s),
        None => println!("Team not found"),
    }

    // 遍历HashMap,这里是对HashMap的引用,因为HashMap后面一般是要继续使用的
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }

    // 更新HashMap
    // 1.覆盖一个值
    scores.insert(String::from("Blue"), 100);
    // 2.只插入一个值,如果键已经存在,则不插入,entry会检查键是否存在
    scores.entry(String::from("Yellow")).or_insert(50);
    // 3.根据旧值更新一个值
    let text = "hello world wonderful world";
    let mut map = HashMap::new();
    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }
    println!("{:?}", map);

    // HashMap函数
    // 默认情况下,HsahMap使用加密功能强大的Hash函数来哈希你的键,可以抵御服务Dos攻击,
    // 它不是最快的,但是很安全的,你可以选择自己定义哈希函数
    
}

错误处理

use std::{
    fs::File,
    io::{ErrorKind, Read},
};
fn main() {
    // 错误处理
    // rust大部分错误是能在编译时处理的,比如类型错误,rust编译器会直接报错
    // 但是有些错误只能在运行时才能发现,比如文件不存在,网络连接失败等,这些错误rust提供了Result和Option两种类型来处理
    // 这种错误分两类,可恢复错误:如文件没找到,和不可恢复错误,如访问超出索引,可恢复错误使用Result类型,不可恢复错误使用panic!宏
    // 在别的语言中,错误通常使用异常处理,但是rust中异常处理是不推荐的,因为异常处理会导致程序的控制流混乱,难以调试

    // 应对panic,既不可恢复的错误
    // Rust会展开调用栈,沿着栈往回走,释放资源,然后终止程序,如果想要在panic时打印错误信息
    // 还可以立即终止程序,不进行展开调用栈,释放资源,直接终止程序,内存需要OS自行释放
    // 如果想要文件体积更小,可以使用std::process::abort函数,不进行展开调用栈,不释放资源,直接终止程序,内存需要OS自行释放

    // 终止错误
    // 在Cargo.toml中添加panic = 'abort',就可以在panic时立即终止程序,不进行展开调用栈,不释放资源,直接终止程序,内存需要OS自行释放
    // [dependencies]
    // [profile.release]
    // panic = 'abort'

    // 命令行 set RUST_BACKTRACE=1 && cargo run 能够打印调用栈,显示出具体的错误位置及原因,等号两边不能有空格

    // 对于可以恢复的错误,就像读取文件,如果没有文件,我们希望创建文件,而不是报错,这时候可以使用Result枚举类型
    // Result枚举有两个变体,Ok和Err,Ok表示成功,Err表示失败,Err中可以包含具体的错误信息
    // enum Result<T, E> {
    //     Ok(T),
    //     Err(E),
    // }
    // Result类型是泛型的,T表示成功时的返回值类型,E表示失败时的错误类型
    // 使用Result类型时,需要使用match语句来处理,match语句可以匹配Result枚举的变体,然后分别处理成功和失败的情况

    test3();
}

fn _test() {
    // 相当于throw
    // panic!("这里出错了");

    let mut v = vec![1, 2, 3];
    v[4]; // 这里会panic,因为索引越界
}

fn _test2() {
    // let f=File::open("test.txt").expect("文件打开失败");
    let f = File::open("test.txt");
    let f = match f {
        Ok(file) => {
            println!("文件打开成功");
            // 这里可以处理文件
            file;
        }
        Err(e) => {
            // 这里可以处理错误
            panic!("文件打开失败,错误信息:{:?}", e);
        }
    };
}

// 判断错误类型,根据不同的错误类型进行不同的处理,感觉有点像是Promise
fn test3() {
    let f = File::open("test.txt");
    let f = match f {
        Ok(file) => {
            println!("文件打开成功");
            // 这里可以处理文件
            file;
        }
        Err(e) => match e.kind() {
            ErrorKind::NotFound => match File::create("test.txt") {
                Ok(file) => {
                    println!("文件创建成功");
                    file;
                }
                Err(e) => {
                    // 这里可以处理错误
                    panic!("文件创建失败,错误信息:{:?}", e);
                }
            },
            other_error => {
                // 这里可以处理其他错误
                panic!("文件打开失败,错误信息:{:?}", other_error);
            }
        },
    };
}

// unwrap_or_else和上面的作用差不多,不过就有点类似 then,它只管处理错误,不处理成功的情况,如果出错,就执行后面的,如果成功,就返回成功的结果
// 如果只是简单的抛错,那就使用 let f = File::open("test.txt").unwrap(),但是这样是没法指定抛错信息的
// 这个时候就可以使用 let f = File::open("test.txt").expect("文件打开失败"),这样就可以指定抛错信息了
fn test4() {
    let f = File::open("test.txt").unwrap_or_else(|e| {
        if e.kind() == ErrorKind::NotFound {
            File::create("test.txt").unwrap_or_else(|e| {
                panic!("文件创建失败,错误信息:{:?}", e);
            })
        } else {
            panic!("文件打开失败,错误信息:{:?}", e);
        }
    });

    // 这个时候就可以使用 let f = File::open("test.txt").expect("文件打开失败"),这样就可以指定抛错信息了
    let f1 = File::open("test.txt").expect("文件打开失败");
}

// 错误传递:一段程序里有多种可能的报错,但是你只想在最后处理一次,这个时候就可以使用错误传递,将错误传递给下一个函数,直到最后处理
fn test5() -> Result<String, std::io::Error> {
    let mut f = match File::open("test.txt") {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => {
            println!("文件内容:{}", s); // 可以在这里打印,但不影响函数返回值
            Ok(s) // 返回读取成功的字符串
        }
        Err(e) => return Err(e),
    }
}
// 等同于
// ?号和from函数
// Trait std::convert::From上的from函数,它可以把一个错误类型转换成另一个错误类型,用于错误间的转换
// 被?的错误,会自动调用from函数,把错误转换成另一个错误类型
// ?号运算符只能返回Result类型,如果返回的是其他类型,那么?号运算符会报错
fn test6() -> Result<String, std::io::Error> {
    let mut f = File::open("test.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}


// Box<dyn Error>:Box<dyn Error>是一个动态分发的错误类型,它可以表示任何实现了Error trait的错误类型
// 这样就可以在main函数中处理任何类型的错误了
// fn main () ->Result<(), Box<dyn std::error::Error>> {
//     let f = File::open("test.txt")?;
//     Ok(())
// }

泛型

fn main() {
    // 泛型就是类型的占位符
    // 编译器会在编译时,根据传入的参数类型,来推导出具体的类型
    // 泛型可以减少代码重复,增加代码的复用性
}
// 函数中使用泛型
// 在比较大小时,要注意,不是所有的类型都可以比较大小,所以需要限制泛型
// 暂时不修改错误,这下面是个错误的例子,只是为了演示泛型
// fn largest<T>(list: &[T]) -> &T {
//     let mut largest = &list[0];

//     for item in list {
//         if item > largest {
//             largest = item;
//         }
//     }

//     largest
// }

// 结构体中使用泛型
fn test1() {
    struct Point<T> {
        x: T,
        y: T,
    }
    // 将泛型放在结构体名后面,表示在类型T上实现方法
    impl<T> Point<T> {
        fn x(&self) -> &T {
            &self.x
        }
    }
    // 在指定类型上实现方法
    impl Point<f32> {
        fn distance_from_origin(&self) -> f32 {
            (self.x.powi(2) + self.y.powi(2)).sqrt()
        }
    }

    let integer = Point { x: 5, y: 10 };

    struct PointTwo<T, U> {
        x: T,
        y: U,
    }

    let both_integer = PointTwo { x: 5, y: 1.0 };
}
// 枚举中使用泛型
fn test2() {
    enum Option<T> {
        Some(T),
        None,
    }

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

trait

use std::fmt::Display;

fn main() {
    // trait
    // trait的作用是定义一个接口,让不同的类型实现这个接口,从而实现多态
    // 只有方法签名,没有具体实现
    // 可以有好多个方法
    // 实现trait的方法,必须实现trait中定义的所有方法,有点像接口,又有点像抽象类

    pub trait Animal {
        fn name(&self) -> &str;
        fn age(&self) -> u32;
        // 默认实现
        fn name_call(&self) -> String {
            format!("{} is a animal", self.name())
        }
    }

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

    impl Animal for Dog {
        fn name(&self) -> &str {
            &self.name
        }

        fn age(&self) -> u32 {
            self.age
        }
    }

    let dog = Dog {
        name: String::from("dog"),
        age: 3,
    };

    println!("name: {}, age: {}", dog.name(), dog.age());
    println!("sound: {}", dog.name_call());
}

fn main2() {
    // 定义一个动物类型
    pub trait Animal {
        fn name(&self) -> &str;
        fn age(&self) -> u32;
        fn name_call(&self) -> String {
            format!("{} is a animal", self.name())
        }
    }

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

    // 实现狗这动物类型的行为
    impl Animal for Dog {
        fn name(&self) -> &str {
            &self.name
        }

        fn age(&self) -> u32 {
            self.age
        }
    }

    // 构建一个狗实例
    let dog = Dog {
        name: String::from("dog"),
        age: 3,
    };

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

    // 实现猫这动物类型的行为
    impl Animal for Cat {
        fn name(&self) -> &str {
            &self.name
        }

        fn age(&self) -> u32 {
            self.age
        }
    }
    // 构建一个猫实例
    let cat = Cat {
        name: String::from("cat"),
        age: 2,
    };

    // 介绍动物,一般的trait作为参数类型
    pub fn introduce(animal: impl Animal) {
        println!("sound: {}", animal.name_call());
    }

    // 作为尖括号泛型来使用
    pub fn introduce1<T: Animal>(animal: T) {
        println!("sound: {}", animal.name_call());
    }

    pub fn introduce2<T: Animal>(animal: T, animal2: T) {
        println!("sound: {}", animal.name_call());
    }

    // trait表示"且"的连接
    pub fn introduce3<T: Animal + Display, U: Clone + Display>(animal: T, animal2: U) {
        println!("sound: {}", animal.name_call());
    }
    // 使用where子句来简化
    pub fn introduce4<T, U>(animal: T, animal2: U)
    where
        T: Animal + Display,
        U:  Clone + Display
    {
        println!("sound: {}", animal.name_call());
    }



    // trait还能作为返回的类型限制
    pub fn get_animal() -> impl Animal {
        Cat {
            name: String::from("cat"),
            age: 2,
        }
    }
}

生命周期标记

use std::fmt::Display;

fn main() {
    test1();
    test2();
    test3();
    test4();
    test5();
}
fn test1() {
    {
        let r;
        // let b=r;这是错的,因为r还没有被初始化,所以不能被赋值给b
        {
            let x = 5;
            // r = &x;这样也是错的,因为x的声明周期比r短,当x离开作用域时,r指向的内存会被释放,所以r指向的内存是无效的
            r = x; //这样是对的,因为x的声明周期比r长,当x离开作用域时,r指向的内存不会被释放,所以r指向的内存是有效的

            // 不难看出,在借用检查器的约束下,借用是不能超出其作用域的。但是直接等就等于值移动给r了,r就是有效的
        }

        println!("The value of r is: {}", r)
    }
}

// 借用的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn test2() {
    let string1 = String::from("abcd");
    let string2 = "xyz";
    // 在 Rust 中,所有引用都必须遵守借用规则,这意味着引用的生命周期不能超出它们所引用的数据的生命周期
    // 在当前的代码中,longest 函数返回了一个指向 x 或 y 的引用,但是我们不知道那个会返回被接着用,也就是不知道哪个生命周期更长
    // 这可能会导致一个悬垂引用(dangling reference),即返回的引用指向了已经不再有效的内存。也就没办法返回
    // 所以我们要用泛型来延长生命周期,所以用&'a str来表示,帮助编译器来计算生命周期

    // 单个借用参数的生命周期标注没啥意义,标注是用来告诉编译器多个引用的生命周期参数之间的关系,意思返回的'a就是借用参数生命周期最短的那个,对比test2和3的例子

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

fn test3() {
    // 这两个生命周期都是比较大的
    let string1 = String::from("abcd");
    let result;
    {
        // 这个生命周期比较小
        let string2 = "xyz";
        // 如果没有'a这个标记,那result可能会是string2的引用
        // 因为string2的生命周期比较短,但是string2的生命周期已经结束了,所以result指向的内存已经无效了,所以需要标注生命周期
        // 它取最小的生命周期,这样编译器就能给我们做检查了
        // 就好比爸妈在房间里,你每天在门外喊一个人借钱,但是现在房间灯熄了,那就不要去敲门喊了,因为你可能在喊你爸,但是你爸睡着了,这时就会打你一顿。这个作为提醒的灯就是'a
        // 那我不想
        result = longest(string1.as_str(), string2);
    }

    println!("The longest string is {}", result);
}
// 为什么会出来'a,其他语言就没有呢?因为别的语言是大部分语言是GC,它会保留生命周期,语言后台帮你管理。
// 而C和C++是手动管理,所以容易空指针。Rust是手动管理内存,为了确定谁的生命周期更短,所以需要标注生命周期

// 结构体的生命周期标注
// 表示part字段的引用不能比整个ImportantExcerpt实例活得更长
// 可能part没有,ImportantExcerpt活着,但是ImportantExcerpt死了,port肯定也没了

// 生命周期标注能省略的三个场景
// 1.引用的每一个参数都有生命周期,那么就省略,因为它们的生命周期是明确的
// 2.如果只有一个输入生命周期,那么就省略,因为它的生命周期就那一个,只要盯着他一个就好,不怕找到已经"死"的 参数
// 3.如果函数没有输入生命周期,那么就省略,因为函数没有输入,所以它没有生命周期,他肯定会"死"的
// 一般来说,生命周期省略规则能覆盖大部分场景,所以你不需要标注生命周期,等到编译器报错的时候,再标注生命周期

// 结构体以及方法中的生命周期标注
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    // 只有一个引用,所以省略生命周期标注
    fn level(&self) -> i32 {
        3
    }
    // 有两个引用,所以需要标注生命周期,但是第一个引用是self,也就是说它的生命周期是明确的
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

fn test4() {
    let novel = String::from("Call me Ishmael. Some years ago...");

    let first_sentence = novel.split('.').next().expect("Could not find a '.'");

    let _i = ImportantExcerpt {
        part: first_sentence,
    };
}

// 静态生命周期
// 所有的字符串字面值都拥有'static生命周期,因为它们在编译时就被确定下来,并且保存在程序的二进制文件中,直到程序结束才被销毁
// 在指定”static“生命周期时,要注意他是否需要引用在整个生命周期存活
fn test5() {
    let s: &'static str = "I have a static lifetime.";
}

// 最后的小例子
// 生命周期标记也是泛型的一种,不过他是来辅助确定引用类型生命周期的,一般泛型是来确定类型的
fn test6<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where
    T: Display,
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

函数测试

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

// 测试函数需要test注解
// #[test]
// 加上了test注解,函数会自动被测试
// 测试函数使用cargo test运行,rust自动构建test Runer可执行文件,会自动运行所有被test注解的函数,并报告其运行是否成功、

// 当使用cargo构建Library时,会自动生成test Module,该模块有一个test函数
// 我们可以添加任意数量的test Module,每个模块中可以包含任意数量的test函数

// rusult 表示成功
// passed 表示测试通过
// failed 表示测试失败
// ignored 表示测试被忽略
// measured 表示测试被测量性能
// filtered 表示测试被过滤

// 测试函数一旦出现了panic,测试就会失败
// 所以,测试函数不能包含任何panic的代码
// 每个测试都是一个独立的线程,因此测试函数之间不能共享状态
// 测试函数不能有参数,也不能返回值
// 一旦某个线程挂掉,那个测试就算是失败的

// 所以在测试中一般使用断言assert宏,而不是panic宏
// assert宏会在断言失败时panic,但不会导致整个测试失败
// assert宏会返回一个布尔值,如果为false,则panic


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

impl Rectangle {
    pub fn can_hold(&self, other: &Rectangle) -> bool {
        self.length > other.length && self.width > other.width
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn larger_can_hold_smaller() {
        // 判断一个矩形是否可以容纳另一个矩形
        let larger = Rectangle {
            length: 8,
            width: 7,
        };
        let smaller = Rectangle {
            length: 5,
            width: 1,
        };

        assert!(larger.can_hold(&smaller));
    }
}

// assert! 宏判断是否为真

// assert_eq! 宏判断是否相等
// assert_ne! 宏判断是否不相等
// 这两个断言失败会自动打印两个参数的值
// 使用Debug宏和PartialEq打印出参数的值(绝大多数的基本类型都实现了Debug和PartialEq)

pub fn add_two(a: i32) -> i32 {
    a + 2
}
#[cfg(test)]
mod tests1 {
    use super::*;

    #[test]
    fn it_adds_two() {
        assert_eq!(4, add_two(2));
    }
}


// 这三个断言是可以添加自定义的错误信息
// 添加了自定义的错误信息是可以让测试失败时,打印出更多的信息,帮助调试
// assert!(larger.can_hold(&smaller), "The larger rectangle cannot hold the smaller one.");
// assert_eq!(5, add_two(2), "Icarus");

// 用should_panic!宏来测试代码是否panic
// 如果panic了,测试就会通过,但是如果你预测会抛错,但是没有测试就会失败。这就说明程序的逻辑有错

pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
    }

    pub fn value(&self) -> i32 {
        self.value
    }
}

#[cfg(test)]
mod tests2 {
    use super::*;

    #[test]
    // #[should_panic]直接写是概括全部的恐慌(没被预知的的错误),还可以像下面那样进行更细致的判断
    #[should_panic(expected = "Guess value must be between 1 and 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}

// 在测试中使用Result<T, E>,因为它也可以通过返回Ok或者Err来表示测试是否通过
// 好处就是它是常用标准库的,不需要再引入  use super::*;
// 坏处就是不能写should_panic!宏了,因为它会返回错误,而不会引发恐慌

#[cfg(test)]
mod tests3 {

    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}

// 控制测试的运行
// 默认的就是并行运行,所有的测试,成功的测试不显示print输出,失败的会显示print输出
// 可以通过改变cargo test命令的参数来控制测试的运行
// cargo test --help可以查看所有参数

// --test <name> 只运行指定的测试
// --test add 其实会测试add1 ,add2...
// --test add div 运行所有以add或div开头的测试

// 在 #[test]下面写#[ignore] 就可以忽略这个测试
// -- --include-ignored 可以运行所有测试,包括被标记为ignored的测试
// -- --ignored 可以运行被标记为ignored的测试


// -- --nocapture 可以让测试的输出显示在终端上,而不是被隐藏起来
// -- --show-output 测试成功也可以让测试的输出显示在终端上,而不是被隐藏起来

// -- --no-run 可以只生成测试的输出,而不运行测试
// -- --quiet 可以关闭测试的输出

// -- --color <when> 可以设置测试的输出颜色,默认是auto
// -- --nocolor 可以关闭测试的输出颜色

// -- --no-fail-fast 可以让测试在遇到第一个失败后继续运行,而不是立即停止
// -- --test-threads <n> 可以设置测试的线程数,默认是1。如果是1就是单线程
// -- --message-format <format> 可以设置测试的输出格式,默认是pretty
// -- --verbose 可以让测试的输出更详细


// 并行测试要求:相互之间没有依赖,不会依赖于某一个共享的状态



// 测试分类
// 单元测试和集成测试
// 单元测试,小,专注,独立,快速,可重复,一次对一个模块进行隔离测试,可以测试private的函数
// 在注解#[cfg(test)]的函数就是单元测试,只有运行cargo test才能编译和运行



// 集成测试,大,复杂,依赖,慢,不可重复, 一次对多个模块进行测试,只能测试public的函数
// 在不同的文件夹中,不需要注解#[cfg(test)]标注,但是要导入被测试函数,还要#[test]标注
// 集成测试一般是为了验证多个测试库的多个部分在一起能不能工作
// 要创建tests目录,和src同级
// tests目录下的每一个文件都是一个单独的包crate
//integration_test.rs

use adder::add_two;
#[test]
fn it_adds_two() {
    assert_eq!(4, add_two(2));
}

// 执行命令:cargo test 会将所有测试函数都执行了,包括tests文件下的所有文件里的函数
// 执行命令:cargo test -- --test integration_test 测试单个文件

// 那如果tests目录下的help函数也会被执行
// 这个时候我们就要在tests目录下建立common目录,这个是rust默认的不会在测试中执行的目录
// 这个时候我们我们就能够使用common目录下的帮助函数了

use adder::add_two;
mod common;
#[test]
fn it_adds_two() {
    common::setup();
    assert_eq!(4, add_two(2));
}

// 如果不是binary crate文件,只是main.rs那就没办法创建集成测试
// 因为没办法将main.rs的函数导入作用域