• rust 初识基础: 变量、数据类型、函数、所有权、枚举


    了解到 rust 和 WebAssembly 的结合使用,可以构建前端应用,而且性能也比较好。初步学习使用
    rust 是预编译静态类型语言。

    安装 rust

    官网下载 rust-CN , 大致了解下为什么选择:高性能、可靠性、生产力。

    打开控制台啊,执行安装 (mac 系统,windwos 或其他系统查看官网)

    &> curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
    • 1

    安装成功时,会打印:

    rust-install.png

    或则会通过查看版本检查是否安装更新:

    $> rustc --version
    
    • 1

    有打印就成功了。

    rust 通过 rustup 来管理。更新 rust

    • rustup update 更新 rust
    • rustup self uninstall 卸载 rust 和 rustup 管理器。

    rust 一些常用的包依赖 c 语言,需要安装 C 编译器

    如果你不是一个 c/c++开发者,则一些常用的 rust 在使用时,会报错。根据不同的系统需要安装 c 编译器。

    • mac 系统安装xcode-select
    $> xcode-select --install
    
    • 1
    • windwos 系统则需要安装visual studio

    visualstudio

    不安装时,报错如图:

    start-error.png

    起步项目 hello world

    rust 的主程序代码文件都以.rs结尾

    $> mkdri rust-web
    $> cd rust-web
    $> vi main.rs
    
    • 1
    • 2
    • 3

    编辑main.rs文件,写入以下内容

    fn main() {
        println!("Hello, world!");
    }
    
    • 1
    • 2
    • 3

    编译文件并执行

    $> rustc main.rs
    $> ./main
    
    • 1
    • 2

    可以看到控制打印输出

    hello-world.png

    main是主函数入口,rust 特殊的函数,最先执行。

    println! 表示调用的是宏(macro) ,它不是普通的函数。所以并不总是遵循与函数相同的规则

    认识 cargo

    Cargo 是 Rust 的构建系统和包管理器,可以帮助我们构建代码、下载依赖库并编译这些库

    通过查看版本检查是否安装:

    $> cargo --version
    
    • 1

    cargo 管理项目,构建工具以及包管理器

    • cargo build 构建项目

      可以通过cargo build --release构建生产包,增加了编译时间,但是的代码可以更快的运行。

    • cargo run 运行项目

    • cargo test 测试项目

    • cargo doc 为项目构建文档

    • cargo publish 将项目发布到 crates.io

    • cargo check 快速检查代码确保其可以编译

    打印成功则安装成功.

    cargo-success.png

    创建一个项目,cargo new rust-web ; 因为我已经创建了项目目录,所以使用cargo init进行初始化

    初始化完目录如下:

    init-project.png

    Cargo.toml 为项目的清单文件。包含了元数据信息以及项目依赖的库,在 rust 中,所有的依赖包称为crates

    [package]
    name = "rust-web"
    version = "0.1.0"
    edition = "2021"
    
    [dependencies]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    src目录则是项目功能文件目录,可以看到文件后缀名是.rs

    fn main() {
        println!("Hello, world!");
    }
    
    • 1
    • 2
    • 3

    启动执行cargo run, 如果启动成功,就会打印出来

    start-success.png

    cargo build构建编译,可以在target目录下看到,

    通过执行./target/debug/rust-web,可以看到和上面输出同样的内容;

    rust 基础语法

    学习一门新语言,首先要掌握它的语法,怎么去写、表达。

    变量、基本类型、函数、注释和控制流

    变量与可变性

    let定义一个变量,更改后打印输出。

    fn main() {
        let age = 24;
        print!("{age}");
        age = 34;
        print!("{age}");
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    执行cargo check,可以看到打印输出,不允许更改。

    immutable-error.png

    如果需要变更,则需要添加mut标识变量可变。但不可以更改变量的类型

    fn main() {
        let mut age = 24;
        print!("{age}");
        age = 34;
        print!("{age}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    const 声明一个常量

    常量声明时,需要明确标注出数据类型。

    fn main() {
        const Age: u32 = 200;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    变量隐藏

    因为不可变性,我们不能对一个变量重复复制,但可以通过对变量的重复声明,来实现变量的重复声明。(变量仍然是不可更改的)

    fn main() {
        let age = 24;
        print!("{age}");
        let age = 34;
        print!("{age}");
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在局部作用域结束后,变量仍为初始声明的值。

    fn main() {
        let age = 24;
        print!("{age}");
        {
            let age = 34;
            print!("{age}");
        }
        print!("{age}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出的值为24 34 24

    数据类型

    了解了数据类型,在声明变量时标明变量的类型。rust 是静态语言,编译时就需要指定所有变量的类型。

    数据类型分为两个大类型:标量(scalar)、复合(compound)。

    标量类型

    表示一个单独的值,有四种基本的类型:整型、浮点型、布尔类型、字符类型。

    整型

    整型是一个没有小数的数字。包括有符号位、无符号位。

    长度有符号无符号
    8-biti8u8
    16biti16u16
    32-biti32u32
    64-biti64u64
    128-biti128u128
    archisizeusize

    有符号位的可以存储 − ( 2 n − 1 ) -(2^{n-1}) (2n1) 2 n − 1 2^{n-1} 2n1的数字。

    isize和usize则依赖运行程序的计算机架构,64 位架构则就是 64 位的,32 位架构就是 32 位的。

    fn main() {
        let num: i16 = -1000;
        print!("{num}");
    }
    
    • 1
    • 2
    • 3
    • 4

    除了十进制数字作为变量值,也可以十六进制、八进制、二进制、Byte(单字节字符)来表示。

    数字字面值示例
    十进制100,250
    十六进制0xff
    八进制0o67
    二进制0b111000
    Byte 单子节字符(仅限于 u8)b’A’

    对于整型变量值还可以通过_做分隔符,以方便读数字,比如23_10,也就是十进制的2310.

    fn main() {
        let num: i16 = -1_000; // 1000
        let age: i8 = 0b1100;  // 12
    }
    
    • 1
    • 2
    • 3
    • 4

    初学者可以使用默认的类型,即不需要书写声明类型,rust 会有一个默认的类型。数字默认是i32

    当我们指定了类型长度后,在编程中可能会出现超出,超过我们指定的存储大小。整型溢出

    浮点型

    浮点型包括f32\f64.所有的浮点数都是有符号的。浮点数采用 IEEE-754 标准表示

    • f32是单精度浮点数
    • f64是双精度浮点数

    rust 默认浮点数类型位f64

    加、减、乘、除、取余操作

    整数除法会向下舍入到最接近的整数

    fn main() {
        let value = 9 / 7; // 1
    }
    
    • 1
    • 2
    • 3
    布尔类型bool: true、false
    字符类型 - char
    fn main() {
        let char = 'a'
    }
    
    • 1
    • 2
    • 3

    用单引号声明 char 字符值,使用双引号声明字符串值。

    复合类型

    复合类型是将多个值组合成一个类型,包括元组、数组。

    元组 tuple

    元组长度固定,声明后就不会被改变。可以由不同类型的值组成。

    fn main() {
        let tup:(i8,u16,i64) = (54,500,1000)
    
        // 通过结构取值
        let (a,b,c) = tup
    
        // 通过下表直接读取
        let a = tup.0;
        let b = tup.1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    不带任何值的元组称为单元元组。

    数组

    数组中的每个数据类型必须相同。数组的长度是固定的。

    fn main() {
        let arr=[34,45,5,67]
    }
    
    • 1
    • 2
    • 3

    数组类型标记为arr:[i32; 4]表示数据类型为i32,共有 4 个元素。

    通过数组的下表访问数组元素arr[0]\arr[1]

    函数

    通过使用fn来定义函数。函数名命名方式建议使用_连接。

    fn main(){
        // 函数体
    }
    
    • 1
    • 2
    • 3

    只要在相同作用域内声明的函数,不管声明在前或在后,都可以调用。

    fn main(){
        println!("hello world");
    }
    
    // 函数声明在调用之后
    fn user_info(){
        // 
        println!("user");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    函数必须声明每一个接受的参数,并且需要指定其数据类型。

    // 函数声明在调用之后
    fn user_info(age: i32){
        // 
        println!("user");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    语句和表达式

    rust是一门基于表达式的语言。其它语言没有,

    语句是执行一些操作但不返回值的指令。表达式计算并产生一个值。

    不能将一个变量赋值给另一个声明的变量

    
    fn main(){
        let num:i32;
        // 这样写是错误的,如果没有指定数据类型,rust会默认指定age数据类型为单元元组
        let age=num=32;
        // 指定age的数据类型,则age不能正常赋值,报错
        let age:i32=num=32;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    let age=num=32 rust会默认指定age为单元元组()不带任何值。num赋值为32;

    通过大括号{} 可声明一个作用域快:

    fn main(){
        let a = {
            let b = 45;
            b+32
            // b+32;
        }
        println!("{a}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看到b+32后面没有加;,就是一个表达式,会有值返回;加了;就是一个语句了。

    语句不会返回值。a的数据类型就是单元元组。

    函数返回值

    函数的最后一个表达式就是函数的返回值。也可以通过return关键字返回指定值。

    函数的返回值必须指定其数据类型

    fn user_info(age: i32)->i32{
        // 
        age*2
    }
    
    • 1
    • 2
    • 3
    • 4

    接受一个参数值,返回其乘积。如果计算超出时类型长度时,需要转换成长度更大的数据类型。

    // TODO:

    fn user_info(age: i8)->i32{
        // 
        age*200
    }
    
    • 1
    • 2
    • 3
    • 4

    控制语句

    if条件判断语句,必须是显示bool类型作为判断条件。rust不会隐世转换数据类型。

    fn user_info(age: u8){
        // 
        if age > 50 {
            println!("中年");
        } else if age > 30 {
            println!("壮年");
        } else if age > 18 {
            println!("青年");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在声明语句中通过条件语句,绑定不同的结果值。所有分支中的数据类型必须是相同的

    fn main(){
        let bool=false;
        let age= if bool {20} else {34};
    }
    
    • 1
    • 2
    • 3
    • 4
    循环语句

    包括三种:loop / while / for

    loop 循环执行,直到终止,也可程序终止通过break

    示例通过循环执行来达到想要的值。break终止循环,通过后面跟表达式来返回表达式的值。

    fn main(){
       let mut num = 50;
       let age = loop{
         num-=5;
         if num<40 {
            break num+1;
         }
       };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当存在多层循环时,break只能循环结束它自己这一层的循环。可以通过增加循环标签,break 可以指定循环结束。

    fn main(){
        let mut count = 0;
        'out_in: loop {
            println!("out");
            let mut num = 10;
            loop {
                println!("{num}");
                if num < 7 {
                    break;
                }
                if count == 4 {
                    break 'out_in;
                }
                num -= 1;
            }
            count += 1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    'out_in 标识循环标签。注意是左单引号。

    while 循环

    fn main(){
        let mut count = 0;
        while count<5{
            println!("{count}");
            count += 1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    if 循环,while更多方便用于条件语句循环执行;if则更适合遍历结构化数据。

    fn main(){
        let arr = [34,56,23,34];
        for val in arr{
            println!("{val}");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    所有权

    这是rust独有的特性。rust无需垃圾回收即可保障内存安全。

    • rust中的每一个值都有一个所有者。
    • 值在任一时刻有且只有一个所有者
    • 当所有者(变量)离开作用域,这个值将被丢弃。

    rust管理内存的方式在变量离开作用域后就会被自动释放。rust在作用于结束后,自动调用内部一个特殊的函数drop

    简单的已知数据长度类型的值存储时存储在中。比如:所有整数、布尔、所有浮点数、字符类型char、元组(其中的每个元素都是已知大小的)

    let a:u8 = 32;
    let b = a;
    
    • 1
    • 2

    这里声明了变量a,并将a的值赋值给了b。b拷贝了a的值存入栈中。也就是栈存储有两个值32.

    而对于一些不可知大小的变量存储,是存放在中的。

    String类型,定义的数据值分配到堆中管理,

    let a = String::from("hboot");
    let b = a;
    
    • 1
    • 2

    此时声明的变量b拷贝了变量a存储在栈中的指针、长度和容量。指针指向仍是堆中同一位置的数据。

    rust为了方便处理内存释放,防止两次内存释放bug产生,变量a被赋值给变量b后,就失效了。不在是一个有效的变量。

    这种行为可以称为所有权转移。转移的变量就不存在了,不可访问。

    那如果想要重复两个变量数据变量,可以通过克隆clone

    let a = String::from("hboot");
    let b = a.clone();
    
    • 1
    • 2

    这样我们存储了双份的数据在内存中。

    在函数中,所有权转移

    在传递参数时,参数会将所有权转移,使得定义的变量失效

    let a = String::from("hboot");
    
    print_info(a); // a的多有权转移到函数print_info中
    //后续访问a则访问不到。
    let b = a.clone();    // 编译会报错,无法执行
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通常日常中,这样会导致一些麻烦,声明的变量值后续还要用。可以通过调用函数返回,重新拿到所有权继续使用该变量

    fn main(){
        let a = String::from("hboot");
    
        let b = print_info(a); // a 所有权转移到函数print_info中
                               // 通过函数返回值所有权又转移到 变量b
    }
    fn print_info(str: String) -> String {
        str
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    为了阻止所有权转来转去,可以通过函数返回元组,来表示只是使用值,不需要所有权

    fn print_info(str: String) -> (String) {
        (str)
    }
    
    • 1
    • 2
    • 3

    每次调用都要注意返回参数,很麻烦。可以通过引用来不转移所有权。

    引用

    通过使用&传参、接参表名只是值引用。而不转移所有权

    fn main(){
        let a = String::from("hboot");
    
        let b: usize = print_info(&a);
    
        // 此处变量a仍然可用
        println!("{}-{}", a, b);
    }
    fn print_info(str: &String) -> usize {
        str.len()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    因为是引用值,print_info函数执行结束,变量str不会有内存释放的操作

    所以引用的变量是不允许更改的。

    通过解引用*进行引用相反的操作。

    如果需要更改引用变量,则需要通过mut

    fn main(){
        let mut a = String::from("hboot");
    
        let b: usize = print_info(&mut a);
    
        // 此处变量a仍然可用
        println!("{}-{}", a, b);
    }
    fn print_info(str: &mut String) -> usize {
        str.push_str(",hello");
        str.len()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    首先声明变量可变,传参创建可变引用&mut,还有函数签名str:&mut String.

    可变引用需要注意的是,同时不能存在多个可变引用。会出现数据竞争导致未定义。也不能在有不可变引用的同时有可变引用。

    let mut a = String::from("hboot");
    
    // 同时多个可变引用是不可行的。
    // 必须等上一个引用结束
    let b = &mut a;
    let c = &mut a; // 这里就会报错
    
    // 这里有用到变量b
    print!("{}-{}",b,c);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当一个函数体执行完毕后,所有使用到的内存都会被自动销毁。如果我们返回了其中变量的引用,则会报错,称为悬垂引用

    fn print_info() -> &mut String {
        let str = String::from("hboot");
    
        // 这是错误的,函数执行完毕,必须交出所有权
        &str
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    转移所有权,不能使用引用作为返回。

    slice 类型

    slice 截取变量数据的一部分作为引用变量。所以它是没有所有权的。

    let str = String::from("hboot");
    
    let substr = &str[0,3]; // hbo
    
    • 1
    • 2
    • 3

    可以看到通过[start..end]来截取字符串的一部分。如果是start是0 可以省略[..3];如果end是包含最后一个字节,则可以省略

    let str = String::from("hboot");
    
    let len = str.len();
    let substr = &str[0..len]; // 同等
    let substr = &str[..];
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看到使用slice的引用变量rust默认类型为&str,这是一个不可变引用。

    现在可以更改函数print_info传参,使得它更为通用

    fn main(){
        let a = String::from("hboot");
    
        // 整个字符串引用
        print_info(&a[..]);
        // 截取引用
        print_info(&a[1..3]);
    
        let b = "nice rust"
        // 也可以传递字符串字面值
        print_info(b);
    }
    fn print_info(str: &str) {
        println!(",hello");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    除了字符串,还有数组可被截取引用。

    let a: [i32; 4] = [3,4,5,12]
    
    let b: &[i32] = &a[1..4]
    
    • 1
    • 2
    • 3

    结构体struct

    通过struct 来定义一个结构体,它是一个不同数据类型的集合。键定义名称,定义键值类型。

    struct User {
        name:String,
        age:i32,
        email:String,
        id:u64
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结构体可以定义变量,这个数据则必须包含结构体中包含的所有字段。

    let user = User {
        name: String::from("hboot"),
        age: 32,
        email: String::from("bobolity@163.com"),
        id: 3729193749,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果需要修改,则需要定义为可变变量let mut user。不允许定义某一个字段可变。

    结构体更新语法可以从其他实例创建一个新实例。

    // 重新创建一个实例,不需要再挨个字段赋值
    let other_user = User {
        name: String::from("admin"),
        ..user
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ..语法指定剩余未显示设置值的字段与给定实例相同的值。必须放在后面,以便其获取给定实例中它没有指定的字段值。

    这里同样适用所有权的转移,我们在实例中重新设置了name,那么原始对象user.name仍然是可访问的。
    对于字段user.email则是不可访问的。它已经转移到other_user.email了。

    不能直接指定结构体的数据类型为$str,在生命周期一节解决这个问题 。

    元组结构体

    创建和元组一样的没有键的结构体。

    struct Color(i32,i32,i32);
    
    • 1

    只指定了数据类型,在一些场景下是有用的。

    let color = Color(124,233,222);
    
    • 1
    类单元结构体

    没有任何字段的结构体。类似于单元元组()

    struct HelloPass;
    
    • 1

    需要在某个类型上实现trait但不需要在类型中存储的时候发挥作用。

    方法语法

    可以在结构体中定义方法。来实现和该结构体相关的逻辑。通过impl关键字定义:

    struct User {
        name: String,
        age: i32,
        email: String,
        id: u64,
    }
    
    impl User {
        fn getAgeDesc(&self) -> &str {
            if self.age > 50 {
                return "中年";
            } else if self.age > 30 {
                return "壮年";
            } else if self.age > 18 {
                return "青年";
            }
    
            return "少年";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    方法也可以接受参数和返回值。方法中的第一个参数self指向结构体实例本身。可以获取结构体中定义的字段数据。

    示例中&self引用,不需要所有权,如果需要控制实例,更改实例数据,则需要更改为&mut self

    定义方法时,也可以定义和属性同名的方法。在调用时,方法需要加();而属性不需要。


    也可以定义self不作为参数的关联函数,这样它就不会作用于结构体实例。这一类函数常用来返回一个结构体新实例的构造函数。

    我们通过元组方式传递结构体需要的四个属性值来创建一个新实例。

    impl User {
        fn admin(user: (String, i32, String, u64)) -> Self {
            Self {
                name: user.0,
                age: user.1,
                email: user.2,
                id: user.3,
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如上,定义了一个关联函数admin,接受一个元组参数,并用其中的四个值来赋值给结构体的几个字段。

    let user_one = User::admin((
        String::from("test"),
        45,
        String::from("123@qq.com"),
        452411232,
    ));
    
    dbg!(&user_one);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样的关联函数需要通过::语法来调用。实例一直在用String::from()是同样的逻辑。

    这样做的好处在于可以免去初始化赋值的麻烦。当然这也需要你知道每个传参定义的是什么数据类型。

    枚举、模式匹配

    枚举就是通过列举所有可能的值来定义一个类型。是将字段和数据值据合在一起。

    通过使用enum来定义枚举值。

    enum Gender {
        Boy,
        Girl,
    }
    
    • 1
    • 2
    • 3
    • 4

    枚举值通常使用驼峰书写。通过::语法实例化枚举值

    let boy = Gender::Boy;
    
    • 1

    可以将枚举作为类型定义在结构体中。这样字段gender的值只能是枚举中定义的。

    struct User {
        name: String,
        age: i32,
        email: String,
        id: u64,
        gender: Gender
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    以上仅仅表达了性别,如果还想表达更多关联的值,除了在结构体定义其他字段来存储,也可以在枚举值绑定数据值表达。

    enum Gender {
        Boy(String,i32),
        Girl(String,i32),
    }
    
    • 1
    • 2
    • 3
    • 4

    附加两个数据值,一个String,一个i32

    let boy = Gender::Boy(String::from("男孩"), 1);
    
    • 1

    也可以将结构体作为枚举数据类型。在枚举中也可以定义譬如结构体的方法。

    impl Gender {
        fn getHobby(&self){
            // 这里可以返回男孩、女孩不同的爱好选项
        }
    }
    
    fn main(){
        let boy = Gender::Boy(String::from("男孩"), 1);
        &boy.getHobby();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Option枚举被广泛运用于处理一个值要么有值要么没值。

    enum Option{
        None,
        Some(T)
    }
    
    fn main(){
        let num1 = 32;
        // 枚举定义的值
        let num2: Option = Some(32); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    他们是不同的,num1类型是i32一个明确有效的值;而num2类型为Option不能确保有值。

    match 控制流结构

    通过match语法可以通过对枚举值的匹配不同执行不同的业务逻辑

    enum Gender {
        Boy,
        Girl,
    }
    
    // 定义一个函数接受gender枚举值作为参数
    // 通过match匹配执行不同的逻辑
    fn get_gender_code(gender: Gender) -> u8 {
        match gender {
            Gender::Boy => {
                print!("男孩");
                1
            }
            Gender::Girl => {
                print!("女孩");
                2
            }
        }
    }
    
    fn main(){
        let boy = Gender::Boy;
        dbg!(getHobby(boy));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    如果枚举绑定了数据,也可以通过匹配模式获取到枚举数据。

    
    // Gender采用之前定义过的有数据绑定的模式
    fn get_gender_code(gender: Gender) -> i32 {
        match gender {
            Gender::Boy(label, code) => {
                print!("{}", label);
                code
            }
            Gender::Girl(label, code) => {
                print!("{}", label);
                code
            }
        }
    }
    
    fn main(){
        let boy = Gender::Boy(String::from("男孩"), 1);
        dbg!(getHobby(boy));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    还有Option也可以被匹配。通过匹配来处理有值的情况下。

    fn plus_one(val: Option) -> Option {
        match val {
            None => None,
            Some(num) => Some(num + 1),
        }
    }
    
    fn main(){
        let num2: Option = Some(32);
    
        // 调用函数执行匹配逻辑
        dbg!(plus_one(num2));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    match匹配要求我们覆盖所有可能的模式。这样的匹配是无穷尽的。

    假设我们只处理某些匹配,其他按默认逻辑处理就好。就需要使用other

    fn plus_two(val: i32) -> i32 {
        match val {
            3 => 3 + 2,
            10 => 10 + 5,
            other => other - 1,
        }
    }
    
    fn main(){
        dbg!(plus_two(10));   // 15
        dbg!(plus_two(4));      // 3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果不想使用匹配的值,通过_处理。

    fn plus_two(val: i32) -> i32 {
        match val {
            3 => 3 + 2,
            10 => 10 + 5,
            _ => -1,
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    除了匹配 3、10,其他值都默认返回-1.

    通过other / _穷举了所有可能的情况。保证了程序的安全性。

    if let丢弃掉match的无穷尽枚举匹配

    通过if let可以仅处理需要匹配处理逻辑的模式,忽略其他模式,而不是使用match的other/_

    fn main(){
        let mut num = 3;
        if let 3 = num {
            num += 2;
        }
    
        dbg!(num); // 5
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    打印输出

    在以上的示例中,我们都是使用 print!或者println!来打印输出。基本类型中基本都是可以打印输出的。

    但其他一些则不能打印输出,比如:元组、数组、结构体等。

    let a = 32; // 正常打印输出 32
    
    let arr = [3,4,5,6];
    
    println!("{}",arr);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    错误打印输出:

    print-error.jpg

    根据错误提示,可以看到书写提示。{}替换为{:?}或{:#?}

    let arr = [3,4,5,6];
    
    println!("{:?}",arr);
    
    • 1
    • 2
    • 3

    再看下结构体的 打印输出

    
    // 直接打印之前定义的User实例 
    println!("{:?}", user)
    
    • 1
    • 2
    • 3

    又报错了,看错误提示:

    struct-error.jpg

    需要增加属性来派生Debug trait。才可以打印结构体实例。

    #[derive(Debug)]
    struct User {
        name: String,
        age: i32,
        email: String,
        id: u64,
    }
    
    fn main(){
        println!("{:?}", user)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    需要注意的是,如果当前这个实例被用来生成其他实例,则其中某些字段的所有权已被转移。

    dbg!

    println!不同,它会接收这个表达式的所有权。println!是引用

    let user = User {
        name: String::from("hboot"),
        age: 32,
        email: String::from("bobolity@163.com"),
        id: 3729193749,
    };
    
    dbg!(user);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    与println!不同的是,会打印出代码行号。如果不希望转移所有权,则可以传一个引用dbg!(&user)

  • 相关阅读:
    思腾云计算
    【MySQL】并发事务产生的问题及事务隔离级别
    Hbase简介(基础介绍 一)
    genEcc256-test
    码神之路项目总结(三)
    JAVA经典百题之求100之内的素数
    腾讯技术创作特训营开班!用写作开启职业生涯新爆点 | 内含福利
    燃料电池汽车践行者
    ffmpeg中examples编译报不兼容错误解决办法
    机器学习概述、特征工程、机器学习算法基础
  • 原文地址:https://blog.csdn.net/heroboyluck/article/details/130911462