首页 > 其他分享 >Rust 入门

Rust 入门

时间:2023-07-02 21:25:31浏览次数:35  
标签:mut 入门 let Rust println main fn String

0x01 准备

(1)安装 Rust

  • 安装 Rust:
    • Windows 系统在官网下载 Rust
    • 版本检测:rustc --version
  • 查看文档:rustup doc
  • 更新 Rust:rustup update
  • 卸载 Rust:rustup self uninstall

在 Clion 中使用 Rust 开发,环境配置指南链接

(2)Hello, World!

  1. 编写

    // filename: main.rs
    fn main() {
        println!("Hello, world!");
    }
    
  2. 编译:rustc main.rs

  3. 运行:./main.exe

  • 分析上述程序:
    • fn main(){}是主函数,是每个 Rust 可执行程序最先运行的代码
    • Rust 的缩进是 4 个空格
    • println!是一个 Rust macro[^宏],如果是函数时就没有!
    • 代码行以;结尾

(3)Cargo

  • Cargo 是 Rust 的构建系统和包管理工具

    • 创建项目:cargo new [projectname]

    • 项目结构:

      graph TB A(Project)-->src -->main.rs A-->Cargo.toml A-->.gitignore
      • Cargo.toml
        • TOML(Tom's Obvious Minimal Language)格式是 Cargo 的配置格式
        • [package]是一个区域标题,表示下方内容是用来配置包的
          • name:项目名
          • version:项目版本
          • authors:项目作者
          • edition:使用 Rust 的版本
        • [dependencies]表示另一个区域的开始,会列出项目的依赖项
        • 代码的包称作 crate
      • src
        • 保存全部源代码
        • 将所有代码移到 src 目录下,创建 Cargo.toml 文件并填写配置信息,从而将项目转换为 Cargo 项目
    • 构建项目:cargo build

      • 生成可执行文件:target\debug[projectname].exe
      • 生成 cargo.lock
        • 用于追踪项目依赖的精确版本
    • 运行项目:cargo run

    • 编译检查:cargo check

    • 发布版本:cargo build --release

0x02 猜数游戏

  • Cargo.toml

    [package]
    name = "Project"
    version = "0.1.0"
    edition = "2021"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies]
    rand = "0.8.5"
    
  • src/main.rs

    use std::io;
    use rand::Rng;
    use std::cmp::Ordering;
    
    fn main() {
        println!("猜数游戏");
        // 生成随机数
        let random_number = rand::thread_rng().gen_range(1..101);
        loop {
            println!("猜测一个数:");
            let mut guess = String::new();
            // 输入字符串
            io::stdin().read_line(&mut guess).expect("无法读取");
            // 字符串转数字及异常处理
            let guess: u32 = match guess.trim().parse() {
                Ok(num) => num,
                Err(_) => { println!("输入错误"); continue; },
            };
            println!("猜测了 {}", guess);
    
            // 比较
            match guess.cmp(&random_number) {
                Ordering::Less => println!("small"),
                Ordering::Greater => println!("BIG"),
                Ordering::Equal => { println!("Win"); break; },
            }
        }
    }
    

0x03 基本概念

(1)变量与可变性

  • 声明变量使用let关键字

    • 默认情况下,变量不可变
    • 声明变量时,在变量前面加上mut使得变量可变
  • 常量(constant)在绑定值后不可变

    • 不可以使用mut修饰

    • 声明常量使用const,其类型编写被标注

    • 常量可以在任何作用域内进行声明,包括全局作用域

    • 常量只可以绑定到常量表达式

    • 在程序运行期间,常量在其声明的作用域内永久有效

    • 常量命名规范:全大写,单词间下划线分隔

      const MAX_SIZE:u32 = 100_000;
      
  • 隐藏(shadowing)

    • 可以使用相同的名字声明新的变量,新的变量就会隐藏之前声明的同名变量

      • 报错写法

        let x = 1;
        x = x + 1;
        
      • 使用隐藏

        let x = 1;
        let x = x + 1;
        
    • mut的区别

      • 如果不使用let关键字那么重新给非mut的变量赋值会导致编译时错误

      • 使用let声明的同名新变量依然不可变

      • 使用let声明的同名新变量可能类型不同

        fn main() {
            let str = "abcde";
            println!("{}", str);	// output: abcde
            let str = str.len();
            println!("{}", str);	// output: 5
        }
        

(2)数据类型

  • 分为标量、复合类型

  • Rust 是静态编译语言,在编译时必须知道所有变量的类型

    • 基于使用的值,编译器通常能推断出它的具体类型。但当可能的类型比较多时,就必须添加类型标注

      • 报错写法

        fn main() {
            let guess = "12345".parse().expect("Error");
            println!("{}", guess);
        }
        
      • 正确写法

        fn main() {
            let guess:u32 = "12345".parse().expect("Error");
            println!("{}", guess);
        }
        

a. 标量类型

  • 一个标量类型代表一个单个的值

  • Rust 有四个主要的标量类型

    • 整数类型

      • 没有小数部分

      • u32代表占据 32 位空间的符号整数

      • i32代表占据 32 位空间的符号整数

      • isizeusize

        • 以上类型的位数由程序运行的计算机架构所决定
        • 一般在对某种集合进行索引操作时使用
      • 整数字面值

        • 除了byte类型外,所有数值字面值都允许使用类型后缀,如1234u8,默认后缀为i32
        Number Literals Example
        Decimal 123_456
        Hex 0xdef
        Octal 0o777
        Binary 0b1100_1000
        Byte (u8 only) b'A'
      • 整数溢出

        • Debug 时,会 panic
        • Release 时,不会 panic,会将数值递归减去范围最大值,直至结果在范围内
    • 浮点类型

      • f32,32 位,单精度
      • f64,64 位,双精度
      • 使用了 IEEE-754 标准描述
      • f64为默认类型
    • 布尔类型

      • 符号为bool
      • 取值分别为truefalse
      • 大小为 1 byte
    • 字符类型

      • 符号为char
      • 大小为 4 byte、
      • 字面值使用单引号
      • 编码方式为 Unicode

b. 复合类型

  • 复合类型可以将多个值放在一个类型里

  • Rust 有两种基础的复合类型

    • 元组(Tuple)

      • 可以将多个类型的多个值放在一个类型里

      • 一旦声明就无法改变元组的长度

      • 创建 Tuple

        fn main() {
            let tup:(i32, f64, u8) = (32, 6.4, 1);
        }
        
      • 获取 Tuple 的元素值

        fn main() {
            let tup:(i32, f64, u8) = (32, 6.4, 1);
            let (x, y, z) = tup;
            println!("{}, {}, {}", x, y, z);
        }
        
      • 访问 Tuple 的元素

        fn main() {
            let tup:(i32, f64, u8) = (32, 6.4, 1);
            println!("{}, {}, {}", tup.0, tup.1, tup.2);
        }
        
    • 数组

      • 其中每个元素类型相同

      • 长度固定

        • 数组类型:[类型; 长度],如[i32; 5]
      • 声明数组

        • 方法一

          fn main () {
              let a = [1, 2, 3, 4, 5];
          }
          
        • 方法二:[初始值; 数组长度]

          fn main () {
              let a = [3; 5];
          }
          
      • 对比

        • 优点
          • 将数据存放在 stack 而非 heap上
          • 保证固定数量的元素
        • 缺点
          • 不如 Vector 灵活
      • 访问元素

        • 数组是 Stack 上分配的单个块的内存

        • 可以使用索引来访问数组元素

          fn main () {
              let a = [3; 5];
              println!("{}", a[3]);	// output: 3
          }
          
        • 索引超出范围时,编译通过,而运行时 panic

(3)函数与注释

a. 函数

  • 使用fn关键字声明函数

  • 命名采用 snake case 方式(全部小写、下划线分隔)

  • 参数

    • parameters/arguments:形参/实参

    • 在函数签名里必须声明每个参数的类型

      fn main () {
          function(3, 4);
      }
      
      fn function(x: i32, y: i32) {
          println!("x={}", x);
          println!("y={}", y);
      }
      
  • 函数体中的语句与表达式

    • 函数体由一系列语句组成,可选的由一个表达式结束
    • Rust 是基于表达式的语言
    • 语句是执行一些动作的指令
    • 表达式会计算产生一个值
    • 函数的定义也是语句
    • 语句不返回值,因此不可使用let将一个语句赋给一个变量
    fn main () {
        let y = {
            let x = 3;
            x + 1
        };
        println!("{}", y);
    }
    
  • 函数的返回值

    • ->符号后声明函数的返回值类型,但不可以为返回值命名
    • Rust 中返回值就是函数体中最后一个表达式的值
    • 若想提取返回,则需使用return关键字,并指定一个返回值
    fn main () {
        let x = 3;
        let y = function(x);
        println!("{}", y);
    }
    
    fn function (x: i32) -> i32 {
        return 10;
    }
    

b. 注释

  • 单行注释:// [content]
  • 多行注释:/* content */

(4)控制流

a. 逻辑判断

  • if表达式

    • 根据bool类型的条件来执行不同的代码分支
    • 与条件相关联的代码块称作分支(arm)
    • 可选添加else
    fn main () {
        let x = 3;
        if x > 10 {
            println!("yes");
        } else { 
            println!("no");
        }
        println!("{}", x);
    }
    
  • 使用else if处理多重条件

    • 当使用了多个else if时,最好使用match重构代码
  • let语句中使用if

    fn main () {
        let mut x = 3;
        let y = if x > 3 {x = 4} else {x = 5};
        println!("{}", x);		// output: 5
    }
    

b. 循环

  1. loop循环

    • 默认无条件循环执行其中代码块
    • 可以使用break终止循环
    fn main () {
        let mut counter = 0;
        let result = loop {
            counter += 1;
            if counter == 10 {
                break counter;
            }
        };
        println!("Result: {}", result);	// output: Result: 10
    }
    
  2. while循环

    • 对某个条件进行判断后决定是否执行循环
    fn main () {
        let mut number = 10;
        while number != 0 {
            println!("{}", number);
            number -= 1;
        }
        println!("{}", number);	// output: 0
    }
    
  3. for循环

    • 主要用于安全、简洁遍历集合
    fn main () {
        let arr = [1, 2, 3, 4, 5];
        for element in arr.iter() {
            println!("{}", element);
        }
    }
    
    • Range

      • 该关键字由标准库提供
      • 在 Range 中指定开始数字b和结束数字e,可生成[b, e)之间的数字
      • 使用.rev()方法可以翻转 Range
      fn main () {
          for element in (1..4).rev() {
              println!("{}", element);
          }
      }
      

0x04 所有权

(1)概述

  • Rust 的核心特性就是所有权
  • 所有程序运行期间都必须管理其使用计算机内存的方式
  • 所有权解决的问题
    • 跟踪代码使用堆中数据情况
    • 最小化堆中数据重复量
    • 清理堆中数据以避免空间不足

a. 栈内存与堆内存

  • 存储数据
    • 栈内存按值接收的顺序来存储,按相反的顺序来读出(后进先出,LastInFirstOut)
    • 栈内存中的数据存取操作名称:压入栈(存储数据)、弹出栈(读出数据)
    • 所有在栈中存储的数据必须拥有已知的固定大小,否则存入堆中
      • 操作系统会在堆中找到一块足够大的空间并返回一个指针,这个过程称为分配
  • 访问数据
    • 在堆中访问数据需要通过指针,比栈慢
  • 函数调用
    • 当调用函数时,函数本地的变量被压到栈上,当函数结束后,这些值就会从栈上弹出

b. 所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值有且仅有一个所有者
  • 当所有者超过作用域(Scope)时将会被删除
    • 变量作用域就是程序中一个项目的有效范围

为演示所有权相关规则,需要提取使用 String 类型数据

  • 举例演示

    • 创建 String 类型的值:let s = String::from("Hello");

      • ::表示from是 String 类型下的函数
    • 字符串插入

      fn main () {
          let mut s = String::from("Hello");
          s.push_str(", world!");
          println!("{}", s);
      }
      

c. 所有权与函数

  • 在语义上,将值传递给函数和把值赋给变量是类似的

    • 将值传递给函数将发生移动或复制
  • 返回值与作用域

    • 函数在返回值的过程中也发生所有权的转移
    • 一个变量的所有权当把一个值赋给其他变量是就会发生移动,当一个包含堆数据的变量离开作用域时,除非数据的所有权被移动到另一个变量上,否则其值就会被 drop 函数清理
  • 如何让函数使用值但不获取其所有权:

    • 将参数传入后再传出以间接保障所有权

      fn main() {
          let s1 = String::from("Hello");
          let (s2, len) = function(s1);
          println!("The length of {} is {}.", s2, len,)	// The length of Hello is 5.
      }
      
      fn function(s: String) -> (String, usize) {
          let length = s.len();
          (s, length)
      }
      
    • 由于上述方法过于麻烦,故而可使用引用来实现

(2)引用与借用

a. 引用

  • &符号标识引用:允许引用其值而不获取其所有权

    fn main() {
        let s1 = String::from("Hello");
        let len = function(&s1);
        println!("The length of {} is {}.", s1, len,)
    }
    
    fn function(s: &String) -> usize {
        s.len()
    }
    
  • 默认引用不可变

b. 借用

  • 把引用作为函数参数的行为叫做借用
  • 借用的东西不可更改

c. 可变引用

  • 可变引用:通过添加mut实现

    fn main() {
        let mut s1 = String::from("Hello");
        let len = function(&mut s1);
        println!("The length of {} is {}.", s1, len,)
    }
    
    fn function(s: &mut String) -> usize {
        s.push_str(", world");
        s.len()
    }
    
  • 重要限制一:在特定作用域内,对某一块数据只能有一个可变引用

    • 防止编译时产生数据竞争

    数据竞争发生原因:

    • 两个或多个指针同时访问同一个数据
    • 至少有一个指针用于写入数据
    • 没有使用任何机制来同步对数据的访问
    • 可以创建新的作用域来允许非同时的创建多个可变引用

      fn main() {
          let mut s = String::from("Hello");
          {
              let s1 = &mut s;
          }
          let s2 = &mut s;
      }
      
  • 重要限制二:禁止同时拥有一个可变引用和一个不变引用

    • 可以拥有多个不变引用

d. 引用的作用域

  • 从声明到最后一次使用

e. 悬空引用

  • 悬空指针:一个指针引用了内存中的某个地址,而这块内存可能已经释放并被重新分配掉
  • 在 Rust 中,编译器可保证引用永远都不是悬空引用。如果引用了某些数据,则编译器将保证在引用离开作用域之前数据不会离开作用域

(3)切片

  • 切片:一种不持有所有权的数据类型

  • 举例:编写一个函数

    • 该函数接受字符串作为参数
    • 返回在这个字符串里找到的第一个单词
    • 当未找到任何空格则返回整个字符串
    fn main() {
        let mut s = String::from("hello world");
        let idx = function(&s);
        println!("{}", idx);
    }
    
    fn function(s: &String) -> usize {
        let bytes = s.as_bytes();
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return i;
            }
        }
        s.len()
    }
    
    • 上述代码存在问题:当在输出前执行s.clear()时,idx的意义就不存在了。为此提出字符串切片
  • 字符串切片

    • 是指向字符串中一部分的引用

    • 形式:[起始索引 .. 终止索引]

      fn main() {
          let s = String::from("hello world");
          let s1 = &s[0 .. 5];
          let s2 = &s[6 .. 11];
          println!("{}, {}", s1, s2);
      }
      
    • 注意:

      • 字符串切片的范围索引必须发生在有效的 UTF-8 字符边界内

      • 当尝试从一个多字节的字符中创建字符串切片时,程序会报错退出

      • 重写上述例子:

        fn main() {
            let mut s = String::from("hello world");
            let s1 = function(&s);
            println!("{}", s1);
        }
        
        fn function(s: &String) -> &str {
            let bytes = s.as_bytes();
            for (i, &item) in bytes.iter().enumerate() {
                if item == b' ' {
                    return &s[..i];
                }
            }
            &s[..]
        }
        
  • 字符串字面值是切片,其被直接存储在二进制程序中

  • 将字符串切片作为参数传递:fn function(s: &str) -> &str {}

    • 此时可以同时接受切片转换成切片的字符串两种类型的数据
    • 定义函数时使用字符串切片来代替字符串引用会使 API 更加通用,且不会损失任何功能
    fn main() {
        let s1 = String::from("hello world");
        let s1_1 = function(&s[..]);
        let s2 = "hello world";
        let s2_2 = function(s2);
    }
    
    fn function(s: &str) -> &str {
        &s[..]
    }
    

0x05 Struct

struct,结构体

  • 自定义的数据类型
  • 为相关联的值命名与打包

(1)定义并实例化 struct

a. 定义 struct

  • 使用 struct 关键字,并为整个 struct 命名

  • 在花括号内,为所有字段定义名称和类型

    struct Student {
        name: String,
        age: u8,
        active: bool,
    }
    

b. 实例化 struct

  • 需要创建 struct 实例

    • 为每个字段指定具体值
    • 无需按声明顺序进行指定
    let mut s = Student {
        name: String::from("SRIGT"),
        age: 22,
        active: true,
    };
    
  • 获取 struct 中的某个值

    • 使用点标记法
    s.name = String::from("SRIGT");
    

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

  • struct 作为函数返回值

    fn create_student(name: String) -> Student {
        Student {
            name: name,
            age: 0,
            active: true,
        }
    }
    
    fn main () {
        let mut s = create_student("SRIGT".to_string());
        println!("{}", s.name);
    }
    
    • 字段初始化简写

      • 当字段名与字段值对应变量名相同时,就可以使用以下简写方法
      fn create_student(name: String, age: u8) -> Student {
          Student {
              name,
              age,
              active: true,
          }
      }
      
      fn main () {
          let mut s = create_student("SRIGT".to_string(), 18);
          println!("{}", s.name);
      }
      
  • struct 更新语法

    • 当需要基于已有的某个 struct 来创建另一个 struct 时使用以下更新方法
    let mut s1 = Student {
        name: String::from("SRIGT"),
        age: 18,
        active: true,
    };
    let mut s2 = Student {
        name: String::from("白月"),
        age: s1.age,
        active: false,
    };
    
    • 简写

      let mut s2 = Student {
          name: String::from("白月"),
          ..s1
      };
      
  • Tuple struct

    • 可以定义类似 tuple 的 struct,其整体有名称,里面的元素没有
    struct Color(i32, i32, i32);
    let black = Color(0, 0, 0);
    
  • Unit-Like Struct

    • 可以定义没有任何字段的 struct
    • 适用于需要某个类型上实现某个 trait,但在里面又没有需要存储的数据时
  • struct 数据所有权

    • 对于struct Student { name: String };,这里的字段使用了 String 而不是&str
      • 该 struct 实例了其拥有的所有数据
      • 只要 struct 实例是有效的,那么里面的字段也是有效的
    • struct 里也可以存放引用,但需要使用生命周期
      • 生命周期中保证只要 struct 实例时有效的,那么里面的引用也是有效的
        • 如果 struct 里面存储引用,而不使用生命周期,就会报错
    #[derive(Debug)]
    struct Node {
        val: i32,
    }
    
    fn main () {
        let node = Node { val: 20};
        println!("{:?}", node);
    }
    

(2)struct 方法

  • 方法与函数类似:fn关键字、名称、参数、返回值

  • 方法与函数区别

    • 方法在 struct(或 enum、trait 对象)的上下文中定义
    • 第一个参数是self,表示方法被调用的 struct 实例

a. 定义方法

  • impl块里定义方法
  • 方法的第一个参数可以是&self,也可以是获得其所有权或可变借用(&mut self
  • 更良好的代码组织
struct node {
    number1: u32,
    number2: u32,
}

impl node {
    fn sum(&self) -> u32 {
        self.number1 + self.number2
    }
}

fn main() {
    let node = node {
        number1: 1,
        number2: 2,
    };
    println!("sum(1, 2) = {}", node.sum());
}
  • 方法调用的运算符

    • Rust 会自动引用或解引用(在调用方法时会发生这种行为)
    • 在调用方法时,Rust 根据情况自动添加&&mut*,以便object可以平铺方法的签名
  • 每个 struct 允许拥有多个impl

b. 关联函数

  • 可以在impl块里定义不把self作为第一个参数的函数,如:String::from()

  • 关联函数通常用于构造器

    • ::符号一般用于关联函数与模块创建的命名空间
    struct node {
        number1: u32,
        number2: u32,
    }
    
    impl node {
        fn equal(number: u32) -> node {
            node {
                number1: number,
                number2: number,
            }
        }
    }
    
    fn main() {
        let node = node::equal(1);
    }
    

0x06 枚举与模式匹配

(1)定义枚举

  • 枚举允许列举所有可能的值来定义一个类型

    enum Kind {
        A,
        B,
    }
    
    fn main() {
        let a = Kind::A;
        let b = Kind::B;
        function(a);
        function(b);
        println!("");
    }
    
    fn function(kind: Kind) {
        match kind {
            Kind::A => println!("A"),
            Kind::B => println!("B"),
        }
    }
    
  • 枚举的变体都位于标识符的命名空间下,使用::进行分隔

    • enum 的变体若附带有值,那么就会在函数的命名空间下生成一个函数

    • 将数据附加到枚举的变体中

      • 不需要额外使用 struct
      • 每个变体可以拥有不同的类型以及关联的数据量
      enum Kind {
          A(String),
          B(u32)
      }
      
      fn main() {
          let a = Kind::A("abc".to_string());
          let b = Kind::B(100);
      }
      
  • 为枚举定义方法

    • 使用empl关键字

(2)Option 枚举

  • 定义于标准库中

  • 在 Prelude(预导入模块)中

  • 描述了某个值可能存在或不存在的情况

  • Rust 中类似 Null 概念的枚举——Option<T>

    • 在标准库中定义为:

      enum Option<T> {
          Some(T),
          None,
      }
      
    • 避免将Option<T>直接当成 T,若想使用Option<T>中的 T,则必须将它转换为 T

(3)控制流运算符 match

  • 允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码

    • 模式可以为字面值、变量名、通配符……
    enum Kind {
        A,
        B
    }
    
    fn change(kind: Kind) -> String {
        match kind {
            Kind::A => String::from("a"),
            Kind::B => String::from("b")
        }
    }
    
    fn main() {}
    
  • 绑定值的模式

    • 匹配的分支可以绑定到任意被匹配对象的部分值
    #[derive(Debug)]
    
    enum Kind2 {
        A,
        B
    }
    
    enum Kind {
        A,
        B(Kind2)
    }
    
    fn change(kind: Kind) -> u8 {
        match kind {
            Kind::A => 1,
            Kind::B(kind2) => {
                println!("{:?}", kind2);
                2
            }
        }
    }
    
    fn main() {
        let b = Kind::B(Kind2::A);
        println!("{}", change(b));
    }
    
  • 匹配Option<T>

    fn main() {
        let one = Some(1);
        let two = Some(one);
        let three = Some(None);
    }
    
    fn function(x: Option<i32>) -> Option<i32> {
        match x {
            Some(x) => Some(x + 1),
            None => None,
        }
    }
    
  • match匹配必须穷举所有的可能

    • 或使用_通配符替代未列出的值

(4)if let

  • 处理只关心一种匹配而忽略其他匹配情况
  • 更少的代码与缩进
  • 放弃了穷举的可能
  • 可看作是match的语法糖
fn main() {
    let x = Some(0u8);
    if let Some(0) = x {
        println!("0")
    }
}

0x07 Package, Crate, Module

(1)Rust 的代码组织

  • 可以暴露或私有的细节
  • 作用域内有效的名称

(2)模块系统

  • Package(包):Cargo 的特性,用于构建、测试、共享 crate

    • 包含一个 Cargo.toml,描述了构建 Crates 的方法
    • 只能包含 0 或 1 个 library crate
    • 可以包含人员数量的 binary crate
    • 但必须至少包含一个 crate
  • Crate(单元包):一个模块树,可产生一个 library 或可执行文件

    • 类型:binary 或 library

    • Crate Root:

      • 是源代码文件,是 Rust 编译器的入口

      Cargo 项目默认设置:

      • src/main.rs:
        • binary crate 的 crate root
        • crate 名与 package 名相同
      • src/lib.rs:
        • package 包含一个 library crate
        • library crate 的 crate root
        • crate 名与 package 名相同
      • Cargo 将 crate root 文件交给 rustc 来构建 library 或 binary
    • Crate 的作用

      • 将相关功能组合到一个作用域内,便于在项目间进行共享
        • 如 rand crate,访问其功能时需要通过其名字 rand
  • Module(模块):利于控制代码的组织、作用域、私有路径

    • 在一个 crate 内将代码进行分组

    • 增加可读性,易于复用

    • 控制项目的私有性(私有边界 privacy boundary)

      • 模块不仅可以组织代码,还可以定义私有边界
      • 如果想把函数或 struct 设为私有,则可将其放入某个模块中
      • Rust 中的所有条目默认私有
      • 父级模块无法访问子模块的私有条目
      • 子模块可以使用所有祖先模块的条目
      • 使用pub关键字可以将某些条目标记为公共的
    • 建立 module

      • 关键字mod
      • 可嵌套
      • 可包含其他项的定义
      mod module_a {
          mod module_a_1 {
              fn function_1() {}
              fn function_2() {}
          }
      
          mod module_a_2 {
              fn function_2() {}
              fn function_3() {}
          }
      }
      
  • Path(路径):为 struct、function 或 module 等项命名的方式

    • 为了在 Rust 的模块中找到某个条目,需要使用路径

    • 路径的形式

      • 绝对路径:从 crate root 开始,使用 crate 名或字面值 crate
      • 相对路径:从当前模块开始,使用selfsuper或当前模块的标识符
    • 路径至少有一个标识符组成,标识符之间使用::

    mod module_outside {
        pub(crate) mod module_inside {
            pub(crate) fn function_1() {}
            pub(crate) fn function_2() {}
        }
    }
    
    pub fn function() {
        crate::module_outside::module_inside::function_1();
        module_outside::module_inside::function_2();
    }
    
    • super关键字可以用来访问父级模块路径中的内容

(3)use关键字

  • 可以使用use关键字将路径导入作用域内

    mod module_outside {
        pub mod module_inside {
            pub fn function_1() {}
        }
    }
    
    use crate::module_outside::module_inside;
    
    pub fn function() {
        module_inside::function_1();
    }
    
  • use的习惯用法

    • 将函数的父级模块引入作用域
    • 指定 struct、enum 等的完整路径
    • 指定同名条目到父级
  • 使用pub use重新导出名称

    • 使用use将路径导入到作用域内后,该名称在此作用域内是私有的
    • 重导出
      • 将条目引入作用域
      • 该条目可以被外部代码引入到其作用域中
  • as关键字

    • as关键字可以为引入的路径指定本地的别名

0x08 常用的集合

(1)Vector

a. Vec<T>称作 vector

  • 由标准库提供
  • 可存储多个值
  • 智能存储相同类型的数据
  • 值在内存中连续存放

b. 创建 Vector

  • Vec::new函数

    fn main() {
        // let vec: Vec<i32> = Vec::new();
        let mut v = Vec::new();
        v.push(1);
        v.push(2);
    }
    
  • 使用初始值创建Vec<T>,使用vec!

    fn main() {
        let v = vec![1, 2, 3, 4];
    }
    

c. 删除 Vector

  • 与任何其他 struct 一样,当 Vector 离开作用域之后就会被清理

  • 读取 Vector 元素

    • 索引(越界:panic)

      fn main() {
          let v = vec![1, 2, 3, 4];
          let first: &i32 = &v[0];
          println!("first: {:?}", first);
          }
      }
      
    • get方法(越界:返回 None)

      fn main() {
          let v = vec![1, 2, 3, 4];
          match v.get(1) {
              Some(x) => println!("v[1]: {}", x),
              None => println!("v[1]: None"),
          }
      }
      

d. 所有权和借用规则

  • 不能再同一作用域内同时拥有可变和不可变引用
fn main() {
    let mut v = vec![1, 2, 3, 4];
    let first= &v[0];
    v.push(5);	// mutable borrow occurs here
    println!("first: {}", first);
}

e. 遍历 Vector 的值

fn main() {
    let mut v = vec![1, 2, 3, 4];
    for i in &v {
        println!("{}", i);
    }
}

基于 vector 使用 enum 来存储多种数据类型

enum list {
 Int(i32),
 Float(f32),
 Str(String),
}

fn main() {
 let vec = vec![
     list::Int(1),
     list::Float(2.0),
     list::Str("3".to_string()),
 ];
}

(2)String

a. 概述

  • Rust 的核心语言层面,只有一个字符串类型:字符串切片 str(或&str
  • 字符串切片:对存储再其他地方、UTF-8 编码的字符串的引用
    • 字符串字面值:存储再二进制文件中,也是字符串切片
  • String 类型
    • 由标准库提供,而非核心语言
    • 可增长、可修改、可拥有
    • 采用了 UTF-8 编码
  • 其他字符串类型:OsString、OsStr、CString、CStr

b. 创建字符串

  • String::new()函数

    fn main() {
        let mut s = String::new();
    }
    
  • 使用初始值来创建 String

    • to_string()方法,可用于实现了 Display trait 的类型,包括字符串字面值

      fn main() {
          let data = "hello";
          let mut s = data.to_string();
      }
      
    • String::from()方法,从字面值创建 String

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

c. 更新 String

  • push_str()方法,把一个字符串切片附加到 String

    fn main() {
        let mut s = String::from("abc");
        s.push_str("def");
        println!("{}", s);
    }
    
  • push()方法,把单个字符附加到 String

    fn main() {
        let mut s = String::from("abc");
        s.push('d');
        println!("{}", s);
    }
    
  • 使用+直接连接字符串

    fn main() {
        let mut s1 = String::from("abc");
        let mut s2 = String::from("def");
        println!("{}", s1+&s2);
    }
    
  • 使用format!宏连接多个字符串

    fn main() {
        let mut s1 = String::from("abc");
        let mut s2 = String::from("def");
        let s = format!("{}-{}", s1, s2);
        println!("{}", s);
    }
    
    • println!()类似,但返回字符串
    • 不会获得参数的所有权

d. 访问 String

  • Rust 的字符串不支持索引语法访问

    • 索引操作应消耗一个常量时间(\(O(1)\)),而 String 无法保证需要遍历的内容是否存在非法字符
  • String 是对Vec<u8>的包装

    • len()方法

      fn main() {
          let len = String::from("abc").len();
          println!("{}", len);
      }
      
    • Rust 中有三种看待字符串的方式

      • 字节 Bytes
      • 标量值 Scalar Values
      • 字形簇 Grapheme Clusters
  • 遍历 String

    • 对于标量值使用chars()方法

      fn main() {
          let s = String::from("abc");
          for i in s.chars() {
              println!("{}", i);
          }
      }
      
    • 对于字节使用bytes()方法

      fn main() {
          let s = String::from("abc");
          for i in s.bytes() {
              println!("{}", i);
          }
      }
      

e. 切割 String

  • 可以使用[]和一个范围来创建字符串的切片(遵循 “ 左闭右开 ” 的范围内取值的规则)

    fn main() {
        let s = String::from("123456");
        let s1 = &s[0..2];
        println!("{}", s1);
    }
    
    • 如果切割时跨越了字符边界,程序就会 panic

(3)HashMap

a. 概述

  • HashMap<K, V>
    • 键值对的形式存储数据,一个键(Key)对应一个值(Value)
    • Hash 函数决定如何在内存中存放 K 和 V
    • 适用于通过任何类型的 K 来寻找对应的 V 数据,而非索引
  • HashMap 使用频率较低,因此不在 Prelude 中
  • 标准库对其支持较少,没有内置的宏来创建 HashMap
  • HashMap 的数据存储在堆中
  • HashMap 是同构的
    • 即在一个 HashMap 中,所有的 K 类型相同、所有的 V 类型相同

b. 创建 HashMap

  • 创建空 HashMap:new()函数
  • 添加数据:insert()方法
use std::collections::HashMap;

fn main() {
    let mut stus = HashMap::new();
    stus.insert(String::from("Amy"), 18);
    stus.insert(String::from("Bob"), 19);
}
  • 可以使用collect方法创建 HashMap

    • 在元素类型为 Tuple 的 Vector 上使用collect方法
      • 要求 Tuple 有两个值,分别作为 K 和 V
      • collect方法可以把数据整合成很多种集合类型
    use std::collections::HashMap;
    
    fn main() {
        let name = vec![String::from("Amy")m String::from("Bob")];
        let age = vec![18, 19];
        let stus: HashMap<_, _> = name.iter().zip(age.iter()).collect();
    }
    

c. HashMap 和所有权

  • 对于实现了 Copy trait 类型(如i32),值会被复制到 HashMap
  • 对于拥有所有权的值(如 String),值会被移动,所有权会被转移给 HashMap

d. 访问 HashMap 中的值

  • get()方法

    • 参数 K
    • 返回值:Option<&V>
    use std::collections::HashMap;
    
    fn main() {
        let mut stus = HashMap::new();
        stus.insert(String::from("Amy"), 18);
        stus.insert(String::from("Bob"), 19);
    
        let age = stus.get("Amy");
        match age {
            Some(age) => println!("age is {}", age),
            None => println!("age is not found"),
        }
    }
    

e. 遍历 HashMap

use std::collections::HashMap;

fn main() {
    let mut stus = HashMap::new();
    stus.insert(String::from("Amy"), 18);
    stus.insert(String::from("Bob"), 19);
    for (k, v) in &stus {
        println!("{}: {}", k, v);
    }
}

f. 更新 HashMap

  • HashMap 的大小可变

  • 每个 K 同时只能对应一个 V

  • 更新 HashMap 中的数据

    • K 已经存在,对应了一个 V

      • 替换现有的 V

        • 强制替换

          use std::collections::HashMap;
          
          fn main() {
              let mut stus = HashMap::new();
              stus.insert(String::from("Amy"), 18);
              stus.insert(String::from("Amy"), 19);
              println!("{:?}", stus)
          }
          
        • 判空后替换

          • 使用entry()方法,检查指定的 K 是否对应一个 V
            • 参数为 K
            • 返回值为enum Entry代表值是否存在
          • Entry 的or_insert()方法:如果 K 不存在则插入新值,否则返回 V 的可变引用
          use std::collections::HashMap;
          
          fn main() {
              let mut stus = HashMap::new();
              stus.insert(String::from("Amy"), 18);
              stus.entry(String::from("Amy")).or_insert(19);
              stus.entry(String::from("Bob")).or_insert(20);
              println!("{:?}", stus)
          }
          
      • 保留现有的 V

      • 合并新旧的 V

        use std::collections::HashMap;
        
        fn main() {
            let mut map = HashMap::new();
            let text = "The man is iron man";
            for word in text.split_whitespace() {
                let cnt = map.entry(word).or_insert(0);
                *cnt += 1;
            }
            println!("{:?}", map)
        }
        
    • K 不存在

      • 添加一对<K, V>

g. Hash 函数

  • 默认情况下,HashMap 使用加密功能强大的 Hash 函数,可以抵抗 DoS 攻击
    • 不是最快,但是安全性高
  • 可以指定不同的 hasher 来切换至另一个 Hash 函数
    • hasher 是实现 BuildHasher trait 的类型

-End-

标签:mut,入门,let,Rust,println,main,fn,String
From: https://www.cnblogs.com/SRIGT/p/17521417.html

相关文章

  • (三)kafka从入门到精通之使用场景
    1、kafka简介Kafka是一种流处理平台,主要用于处理大量数据流,如实时事件、日志文件和传感器数据等。Kafka的目的是实现高吞吐量、低延迟和高可用性的数据处理。Kafka提供了一个高度可扩展的架构,可以轻松地添加和删除节点,并且能够处理数百亿条消息/分区。Kafka的消息可以容错,即......
  • (四)kafka从入门到精通之安装教程
    1、kafka和zookeeper简介Kafka是一个高性能、低延迟、分布式的分布式数据库,可以在分布式环境中实现数据的实时同步和分发。Zookeeper是一种开源的分布式数据存储系统,它可以在分布式环境中存储和管理数据库中的数据。它的主要作用是实现数据的实时同步和分发,可以用于实现分布......
  • Filter-快速入门
       ......
  • kafka入门必备知识
    1.Kafka是一个分布式流处理平台:可以让你发布和订阅流式的记录。这一方面与消息队列或者企业消息系统类似。可以储存流式的记录,并且有较好的容错性。可以在流式记录产生时就进行处理。2.消息系统:定义将数据从一个应用程序传递到另一个应用程序,通过提供消息传递和消......
  • (一)kafka从入门到精通之初识kafka
    一、发布订阅系统在学习kafka之前,我们先来看看什么是发布订阅系统。概念数据的发送者不会直接把消息发送给接收者,这是发布与订阅消息系统的一个特点。发布者以某种方式对消息进行分类,接受者订阅它们,以便接受特定类型的消息。发布与订阅系统一般会有一个broker,也就是发布消息的......
  • (二)kafka从入门到精通之kafka的优势
    学习传送门(一)kafka从入门到精通之初识kafka一、常用消息队列比较基于发布与订阅的消息系统那么多,为什么Kafka会是一个更好的选择呢?咱们先来简单的看看mq的一个对比图吧。特性ActiveMQRabbitMQRocketMQKafka生产者消费者模式支持支持支持支持发布订阅......
  • 入门前端学习内容
    html css js es6(重要)axiosgitwebpacknodejsvue2和3uniapp,react和Typescript可以等到工作后再学UI组件库可以学下ElementUI,其中webpack和nodejs了解即可,其他的最好认真学习......
  • Kong入门学习实践(8)流量控制插件
    Kong的一大特色就在于强大的可扩展性,具体实现方式就是插件。一来Kong已经提供了很多内置的插件,二来我们也可以使用Lua语言自定义开发插件。今天,我们就来了解一些常用的流量控制插件。关于流量控制插件我们在实际应用往往会有一些场景需要限流和限频,从而管理入站和出站的流量。......
  • ASP.NET Core SignalR 入门
    本章将和大家分享使用SignalR生成实时应用的基础知识。通过本文您将学习如何:使用ASP.NETCoreSignalR+MVC+Vue2.x+require最终创建一个正常运行的简易聊天应用。废话不多说,我们直接来看一个Demo,Demo的目录结构如下所示:本Demo的Web项目为ASP.NETCoreWeb应用程序(目......
  • Linux 图形栈从入门到放弃 --- Linux 图形相关概念简介
    PS:要转载请注明出处,本人版权所有。PS:这个只是基于《我自己》的理解,如果和你的原则及想法相冲突,请谅解,勿喷。环境说明  无前言  在日常生活中,像我们常用的ubuntu(Linux),windows,android,mac等等系统,我们都能够看到丰富的图形界面。此外,如果大家了解过以前的嵌入式系统,很多......