语言基础

变量与可变性

变量默认是不可改变的(immutable),不能对不可变变量二次赋值

  • 使用 let 创建变量,变量不能重复赋值
  • 在变量名前添加 mut 来使其可变,但不能改变其类型
  • 使用 const 创建常量(使用下划线分隔的大写字母单词),不可对其使用 mut
  • 隐藏(shadowing):用相同的变量名来隐藏前一个变量,可改变其类型

    1. let spaces = " ";
    2. let spaces = spaces.len();

    数据类型

    Rust 是静态类型语言(statically typed),在编译时必须知道所有变量的类型。

  • 标量(scalar)

  • 复合(compound)

    标量(scalar)

    代表一个单独的值

  • 整型

  • 浮点型
  • 布尔
  • 字符类型

    整型

    微信截图_20210118164849.png

    浮点型

  • f64:默认类型,双精度浮点数

  • f32:单精度浮点数

    1. fn main() {
    2. let x = 2.0; // f64
    3. let y: f32 = 3.0; // f32
    4. }

布尔值

  1. fn main() {
  2. let t = true;
  3. let f: bool = false; // 显式指定类型注解
  4. }

字符类型(the character type)

Rust 的 char 类型是语言中最原生的字母类型(注意 char 由单引号指定,不同于字符串使用双引号。)

  1. fn main() {
  2. let c = 'z';
  3. let z = 'ℤ';
  4. let heart_eyed_cat = '😻';
  5. }

复合(compound)

  • 元组(tuple)
  • 数组(array)

    元组(tuple)

    将多个其他类型的值组合进一个复合类型,使用包含在圆括号中的逗号分隔的值列表来创建一个元组

  1. fn main() {
  2. let tup = (500, 6.4, 1);
  3. // 解构(destructuring)
  4. let (x, y, z) = tup;
  5. // 使用点号(.)后跟值的索引来直接访问
  6. let five_hundred = x.0;
  7. let six_point_four = x.1;
  8. let one = x.2;
  9. println!("The value of y is: {}", y);
  10. }

数组(array)

数组中的每个元素的类型必须相同。Rust 中的数组是固定长度的(一旦声明,它们的长度不能增长或缩小)

  1. // [类型; 长度]
  2. let a: [i32; 5] = [1, 2, 3, 4, 5];

函数

Rust 中的函数定义以 fn 开始并在函数名(所有字母都是小写并使用下划线分隔单词)后跟一对圆括号。大括号告诉编译器哪里是函数体的开始和结尾。

  • 函数参数:声明时必须指定类型
  • 如果有返回值,要在箭头(->)后声明它的类型

    语句和表达式

    Rust 是一门基于表达式(expression-based)的语言

  • 语句(Statements)是执行一些操作但不返回值的指令。

  • 表达式(Expressions)计算并产生一个值,{}也是一个表达式,表达式的结尾没有分号 ```rust fn main() { let x = plus_one(5);

    println!(“The value of x is: {}”, x);

    let y = {

    1. let x = 3;
    2. // 表达式的结尾没有分号
    3. x + 1

    };

    println!(“The value of y is: {}”, y); }

fn plus_one(x: i32) -> i32 { x + 1 }

  1. <a name="dNnIJ"></a>
  2. ## 控制流(Control Flow)
  3. - if
  4. - loops
  5. - while
  6. - for
  7. <a name="Iuw9x"></a>
  8. ### if 表达式
  9. > Rust 不会尝试自动地将非布尔值转换为布尔值
  10. ```rust
  11. fn main() {
  12. let number = 6;
  13. if number % 4 == 0 {
  14. println!("number is divisible by 4");
  15. } else if number % 3 == 0 {
  16. println!("number is divisible by 3");
  17. } else if number % 2 == 0 {
  18. println!("number is divisible by 2");
  19. } else {
  20. println!("number is not divisible by 4, 3, or 2");
  21. }
  22. }

if 的每个分支的可能的返回值都必须是相同类型

  1. fn main() {
  2. let condition = true;
  3. let number = if condition {
  4. 5
  5. } else {
  6. //这里只能返回数字类型
  7. 6
  8. };
  9. println!("The value of number is: {}", number);
  10. }

loops

loop 关键字告诉 Rust 一遍又一遍地执行一段代码,可以使用 break 关键字来告诉程序何时停止循环

  1. fn main() {
  2. let mut counter = 0;
  3. let result = loop {
  4. counter += 1;
  5. if counter == 10 {
  6. break counter * 2;
  7. }
  8. };
  9. assert_eq!(result, 20);
  10. }

while

  1. fn main() {
  2. let mut number = 3;
  3. while number != 0 {
  4. println!("{}!", number);
  5. number = number - 1;
  6. }
  7. println!("LIFTOFF!!!");
  8. }

for

  1. fn main() {
  2. let a = [10, 20, 30, 40, 50];
  3. for element in a.iter() {
  4. println!("the value is: {}", element);
  5. }
  6. }

所有权(ownership)

Rust 通过所有权系统管理内存

什么是所有权

所有权规则

  1. Rust 中的每一个值都有一个被称为其 所有者owner)的变量
  2. 值有且只有一个所有者
  3. 当所有者(变量)离开作用域(scope),这个值将被丢弃

    堆和栈

  4. 编译时数据的类型大小是固定的,分配在栈上

  5. 数据类型大小不固定,分配在堆上

    变量作用域

    String 类型

    这个类型被分配到堆上,所以能够存储在编译时未知大小的文本。可以使用 from 函数基于字符串字面值来创建 String

  1. let mut s = String::from("hello");
  2. s.push_str(", world!"); // push_str() 在字符串后追加字面值
  3. println!("{}", s); // 将打印 `hello, world!`

对于 String 类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容。这意味着:

  • 必须在运行时向操作系统请求内存
  • 需要一个当我们处理完 String 时将内存返回给操作系统的方法

    内存与分配

    Rust 采取的策略:内存在拥有它的变量离开作用域后就被自动释放

  1. {
  2. let s = String::from("hello"); // 从此处起,s 是有效的
  3. // 使用 s
  4. } // 此作用域已结束,
  5. // s 不再有效

在变量离开作用域时 Rust 会调用一个 drop 函数并清理变量的堆内存

变量与数据交互的方式(一):移动(Move)

在进行赋值(let x = y)或通过值来传递函数参数(foo(x))的时候,资源 的所有权(ownership)会发生转移。资源只能拥有一个所有者。这防止了 资源的重复释放。在移动资源之后,原来的所有者不能再被使用

  1. let s1 = String::from("hello");
  2. let s2 = s1;

微信截图_20210118181006.png

变量与数据交互的方式(二):克隆(Clone)

使用 Clone 函数深度复制 String 中堆上的数据,而不仅仅是栈上的数据

  1. let s1 = String::from("hello");
  2. let s2 = s1.clone();
  3. println!("s1 = {}, s2 = {}", s1, s2);

只在栈上的数据:拷贝
  • 所有整数类型
  • 布尔
  • 浮点数
  • 字符类型,char
  • 元组,当且仅当包含的类型也都是 Copy 的时候。 ```rust let x = 5; let y = x;

println!(“x = {}, y = {}”, x, y);

  1. <a name="xHNM5"></a>
  2. #### 所有权与函数
  3. ```rust
  4. fn main() {
  5. let s = String::from("hello"); // s 进入作用域
  6. takes_ownership(s); // s 的值移动到函数里 ...
  7. // ... 所以到这里不再有效
  8. let x = 5; // x 进入作用域
  9. makes_copy(x); // x 应该移动函数里,
  10. // 但 i32 是 Copy 的,所以在后面可继续使用 x
  11. } // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
  12. // 所以不会有特殊操作
  13. fn takes_ownership(some_string: String) { // some_string 进入作用域
  14. println!("{}", some_string);
  15. } // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
  16. fn makes_copy(some_integer: i32) { // some_integer 进入作用域
  17. println!("{}", some_integer);
  18. } // 这里,some_integer 移出作用域。不会有特殊操作

返回值与作用域

  1. fn main() {
  2. let s1 = gives_ownership(); // gives_ownership 将返回值
  3. // 移给 s1
  4. let s2 = String::from("hello"); // s2 进入作用域
  5. let s3 = takes_and_gives_back(s2); // s2 被移动到
  6. // takes_and_gives_back 中,
  7. // 它也将返回值移给 s3
  8. } // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
  9. // 所以什么也不会发生。s1 移出作用域并被丢弃
  10. fn gives_ownership() -> String { // gives_ownership 将返回值移动给
  11. // 调用它的函数
  12. let some_string = String::from("hello"); // some_string 进入作用域.
  13. some_string // 返回 some_string 并移出给调用的函数
  14. }
  15. // takes_and_gives_back 将传入字符串并返回该值
  16. fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
  17. a_string // 返回 a_string 并移出给调用的函数
  18. }

引用(References)与借用(Borrowing)

使用值但不获取其所有权,函数签名使用 & 来表明参数 s 的类型是一个引用。将获取引用作为函数参数称为 借用borrowing)。默认不允许修改引用的值。

  1. fn main() {
  2. let s1 = String::from("hello");
  3. let len = calculate_length(&s1);
  4. println!("The length of '{}' is {}.", s1, len);
  5. let r1 = String::from("hello");
  6. let r2 = &mut r1;
  7. r2.push_str(", hhh");
  8. }
  9. fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
  10. s.len()
  11. } // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
  12. // 所以什么也不会发生

微信截图_20210118201007.png

可变引用(Mutable References)

  • 先将变量设为可变变量 mut s
  • 再创建一个可变引用 &mut s

在特定作用域中的特定数据有且只有一个可变引用,就是说同一个作用域你不能搞出两个 &mut s
不能在拥有不可变引用的同时拥有可变引用,就是你不能同时使用 &s 和 &mut s

  1. fn main() {
  2. let mut s = String::from("hello");
  3. change(&mut s);
  4. }
  5. fn change(some_string: &mut String) {
  6. some_string.push_str(", world");
  7. }

垂直引用(Dangling References)

就是你引用到了一个已经释放了内存的变量

  1. fn dangle() -> &String { // dangle 返回一个字符串的引用
  2. let s = String::from("hello"); // s 是一个新字符串
  3. &s // 返回字符串 s 的引用
  4. } // 这里 s 离开作用域并被丢弃。其内存被释放。
  5. // 危险!

应该直接把所有权移动出去

  1. fn main() {
  2. let reference_to_nothing = dangle();
  3. }
  4. fn no_dangle() -> String {
  5. let s = String::from("hello");
  6. s
  7. }

引用的规则

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用
  • 引用必须总是有效

    切片(slices)

    slice是一个没有所有权的数据类型,其允许你引用集合中一段连续的元素序列,而不引用整个集合。 Slices are borrowed, that’s to say, they are passed by reference to a function and a slice’s size is determined at runtime.

  1. let s = String::from("hello world");
  2. let len = s.len();
  3. let hello = &s[0..5];
  4. //= 意味着包含最后的数字
  5. let hellos = &s[0..=4];
  6. let slice = &s[..2];
  7. let slice = &s[3..];
  8. let slice = &s[0..len];
  9. let slice = &s[..];

结构体(Structs)

struct,或者 structure,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。 在大括号中,定义每一部分数据的名字和类型,我们称为 字段field

  1. struct User {
  2. username: String,
  3. email: String,
  4. sign_in_count: u64,
  5. active: bool,
  6. }
  7. let user1 = User {
  8. email: String::from("someone@example.com"),
  9. username: String::from("someusername123"),
  10. active: true,
  11. sign_in_count: 1,
  12. };
  13. //.. 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值
  14. let user2 = User {
  15. email: String::from("another@example.com"),
  16. username: String::from("anotherusername567"),
  17. ..user1
  18. };
  19. fn build_user(email: String, username: String) -> User {
  20. User {
  21. email,
  22. username,
  23. active: true,
  24. sign_in_count: 1,
  25. }
  26. }

方法(Method)

依附于对象的函数,第一个参数总是 self,它代表调用该方法的结构体实例。 在 impl 代码块中定义

  1. #[derive(Debug)]
  2. struct Rectangle {
  3. width: u32,
  4. height: u32,
  5. }
  6. impl Rectangle {
  7. fn area(&self) -> u32 {
  8. self.width * self.height
  9. }
  10. }
  11. fn main() {
  12. let rect1 = Rectangle { width: 30, height: 50 };
  13. println!(
  14. "The area of the rectangle is {} square pixels.",
  15. rect1.area()
  16. );
  17. }

枚举(Enum)

枚举 enum,用于从众多选项中选择一个

  1. enum IpAddrKind {
  2. V4,
  3. V6,
  4. }
  5. struct IpAddr {
  6. kind: IpAddrKind,
  7. address: String,
  8. }
  9. let home = IpAddr {
  10. kind: IpAddrKind::V4,
  11. address: String::from("127.0.0.1"),
  12. };
  13. let loopback = IpAddr {
  14. kind: IpAddrKind::V6,
  15. address: String::from("::1"),
  16. };

用枚举代替结构体

可以给枚举的每一个成员,指定一个数据类型,并且在创建一个枚举变量的时候,将某个值存入枚举

  1. // 定义一个操作枚举
  2. #[derive(Debug)]
  3. enum Operation {
  4. Move {x: i32, y:i32},
  5. Jump(u32),
  6. Attack(i32),
  7. }
  8. fn main() {
  9. // 定义一个移动的操作
  10. let opt_move = Operation::Move {x: 10, y: 11};
  11. // 定义一个攻击的操作
  12. let opt_attack = Operation::Attack(100);
  13. // 定义一个跳跃的操作
  14. let opt_jump = Operation::Jump(3);
  15. DoOperation(opt_move);
  16. DoOperation(opt_attack);
  17. DoOperation(opt_jump);
  18. }
  19. // 执行操作
  20. fn DoOperation(opt: Operation) {
  21. println!("Do operation: {:?}", opt);
  22. }

Option 枚举

使用 Option 作为存在值和空值间的选择,因为 Option 可以被赋予指定类型,即使当变量还为初始化时,也是明确变量应该被设置的类型

  1. enum Option {
  2. Some(T),
  3. None,
  4. }
  1. let some_number = Some(5);
  2. let some_string = Some("a string");
  3. let absent_number: Option<i32> = None; //当变量暂不可知时,赋予None

match 控制流运算符

match 表达式是一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。 the match keyword which behaves the same way as a switch statement would (Rust does not have switch).

  1. let num = 101;
  2. if num < 100 {
  3. println!("{} is less than 100", num)
  4. } else if num > 100 {
  5. println!("{} is more than 100", num)
  6. } else if num == 100 {
  7. println!("{} is equal 100", num)
  8. }

match 表达式执行时,它将结果值按顺序与每一个分支的模式相比较。如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支。
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。

  1. enum Choice {
  2. One,
  3. Two,
  4. Three,
  5. }
  6. //suppose this was passed by the user and you stored in a variable
  7. let choice = Choice::One;
  8. match choice {
  9. Choice::One => println!("Option 1"),
  10. Choice::Two => println!("Option 2"),
  11. Choice::Three => println!("Option 3"),
  12. }

匹配 Option

  1. fn plus_one(x: Option<i32>) -> Option<i32> {
  2. match x {
  3. None => None,
  4. Some(i) => Some(i + 1),
  5. }
  6. }
  7. let five = Some(5);
  8. let six = plus_one(five);
  9. let none = plus_one(None);

_ 通配符

_ 模式会匹配所有的值。通过将其放置于其他分支之后,_ 将会匹配所有之前没有指定的可能的值。

  1. let some_u8_value = 0u8;
  2. match some_u8_value {
  3. 1 => println!("one"),
  4. 3 => println!("three"),
  5. 5 => println!("five"),
  6. 7 => println!("seven"),
  7. _ => (),
  8. }

if let 简洁控制流

  1. # #[derive(Debug)]
  2. # enum UsState {
  3. # Alabama,
  4. # Alaska,
  5. # }
  6. #
  7. # enum Coin {
  8. # Penny,
  9. # Nickel,
  10. # Dime,
  11. # Quarter(UsState),
  12. # }
  13. # let coin = Coin::Penny;
  14. let mut count = 0;
  15. match coin {
  16. Coin::Quarter(state) => println!("State quarter from {:?}!", state),
  17. _ => count += 1,
  18. }
  19. if let Coin::Quarter(state) = coin {
  20. println!("State quarter from {:?}!", state);
  21. } else {
  22. count += 1;
  23. }

包管理 (Packages)

  • Packages)是 Cargo 的一个功能,它允许你构建、测试和分享 crate。
  • Crates 是一个模块的树形结构,它形成了库或二进制项目。
  • 模块Modules)和 use 关键字允许你控制作用域和路径的私有性。
  • 路径path)是一个命名例如结构体、函数或模块等项的方式

    包和 crate 用来创建库和二进制项目

  • crate 是一个二进制或库项目

  • crate 根crate root)是一个用来描述如何构建 crate 的文件
  • 带有 Cargo.toml 文件的 用以描述如何构建一个或多个 crate。一个包中至多可以有一个库项目

    1. //当运行 cargo new 时是在创建一个包
    2. $ cargo new my-project
    3. Created binary (application) `my-project` package
    4. $ ls my-project
    5. Cargo.toml
    6. src
    7. $ ls my-project/src
    8. main.rs

    Cargo 的约定配置:

  • 与 Cargo.toml 同级目录下的 src 中包含 main.rs ,默认为 crate 根,则包拥有同名二进制 crate

  • src 中包含 lib.rs 也做为 crate 根,则包带有同名的库 crate
  • 包可以带有多个二进制 crate,需将其文件置于 src/bin 目录;每个文件将是一个单独的二进制 crate

    模块系统(the module system)用来控制作用域和私有性

    微信截图_20210119141004.png

    the compiler only sees the crate module which is our main.rs file. This is because we need to explicitly(显式) build the module tree in Rust - there’s no implicit(隐式) mapping between file system tree to module tree.

  • The mod keyword declares a submodule(子模块)

  • The pub keyword makes things public
  • extern crate is like mod, but it declares an external crate dependency instead of a submodule.
  • The super keyword in module path refers to the parent scope
  • The use keyword is used to shorten(缩短) the module path
  • External dependencies(added to Cargo.toml) are globally available to all modules inside a project ```rust // main.rs mod config; mod routes; mod models;

fn main() { routes::health_route::print_health_route(); routes::user_route::print_user_route(); config::print_config(); println!(“main”); }

  1. ```rust
  2. // routes/mod.rs
  3. pub mod health_route;
  4. pub mod user_route;
  1. // routes/user_route.rs
  2. //use crate::models::user_model::print_user_model;
  3. use crate::models::user_model::print_user_model as log_user_model;
  4. pub fn print_user_route() {
  5. crate::models::user_model::print_user_model();
  6. //print_user_model();
  7. log_user_model();
  8. crate::routes::health_route::print_health_route();
  9. // can also be called using
  10. super::health_route::print_health_route();
  11. println!("user_route");
  12. }
  1. // models/mod.rs
  2. pub mod user_model;
  1. // models/user_model.rs
  2. // 引用外部依赖
  3. use rand::random;
  4. pub fn print_user_model() {
  5. let random_number: u8 = random();
  6. println!("{}", random_number);
  7. println!("user_model");
  8. }

集合(Collections)

集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知并且可以随着程序的运行增长或缩小

vector(矢量)

vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。

creating new vector

  • call the Vec::new function with the specified datatype
  • using the vec! macro(宏). The macro will create a new vector which holds the given initial values.

    1. fn main() {
    2. let mut data: Vec<i32> = Vec::new();
    3. let mut datas = vec![5, 10, 15];
    4. let mut dataTwo = vec![5;10];
    5. }

    add Items

  • using the push method

    1. fn main() {
    2. let mut data = Vec::new(); // Create a mutable empty vector
    3. data.push(5); // adding values dynamically
    4. data.push(10);
    5. }

    access Items

    1. fn main() {
    2. let mut v = vec![5, 10, 15];
    3. let one: &i32 = &v[0];
    4. println!("one = {}", one);
    5. println!("one = {}", *one);
    6. //(2)推荐的方法
    7. match v.get(3) {
    8. Some(value) => println!("value = {}", value),
    9. _ => println!("None"),
    10. }
    11. }

    change item value

    1. fn main() {
    2. let mut data = vec![5, 10, 15];
    3. data[0] = 15;
    4. println!("{}", &data[0]); // Output 15
    5. }

    remove item

    1. fn main() {
    2. let mut data = vec![5, 10, 15];
    3. data.remove(0); // 5 removed
    4. //The pop() method removes the last item from a vector
    5. data.pop();
    6. //The clear() method removes all values from a vector
    7. data.clear();
    8. println!("{}", data.len()); // 0
    9. }

    遍历

    ```rust let mut v2: Vec = Vec::new(); v2.push(1); v2.push(2); v2.push(3);

    //6 //(1)不可变的遍历 for i in &v2 {

    1. println!("i = {}", i);

    }

    //(2)可变的遍历 for i in &mut v2 {

    1. *i += 1;
    2. println!("i = {}", i);

    }

  1. <a name="k8LzI"></a>
  2. ## 字符串(String)
  3. > 可增长的、可变的、有所有权的、UTF-8 编码的字符串类型
  4. <a name="77j9k"></a>
  5. ### 创建
  6. - 以 `new` 函数创建字符串开始
  7. - 使用 `to_string` 方法
  8. - 使用 `String::from` 函数
  9. ```rust
  10. let mut s = String::new();
  11. let data = "initial contents";
  12. let s = data.to_string();
  13. // 该方法也可直接用于字符串字面值:
  14. let s = "initial contents".to_string();
  15. let s = String::from("initial contents");

更新

使用 push_strpush 附加字符串

  1. let mut s = String::from("foo");
  2. s.push_str("bar");
  3. let mut s = String::from("lo");
  4. s.push('l');

使用 + 运算符或 format! 宏拼接字符串

  1. let s1 = String::from("Hello, ");
  2. let s2 = String::from("world!");
  3. // 这个语句会获取 s1 的所有权,附加上从 s2 中拷贝的内容,并返回结果的所有权
  4. let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
  5. let s1 = String::from("tic");
  6. let s2 = String::from("tac");
  7. let s3 = String::from("toe");
  8. // 不会获取任何参数的所有权
  9. let s = format!("{}-{}-{}", s1, s2, s3);

Hash Maps

HashMap<K, V> 类型储存了一个键类型 K 对应一个值类型 V 的映射 所有的键必须是相同类型,值也必须都是相同类型

新建

  • 使用标准库中集合部分的 HashMap
  • 在两个 vector 中,使用 zip 方法来创建一个元组的 vector,接着使用 collect 方法将这个元组 vector 转换成一个 HashMap ```rust use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from(“Blue”), 10); scores.insert(String::from(“Yellow”), 50);

let team_name = String::from(“Blue”); // 通过 get 方法并提供对应的键来从哈希 map 中获取值 let score = scores.get(&team_name);

for (key, value) in &scores { println!(“{}: {}”, key, value); }

  1. ```rust
  2. use std::collections::HashMap;
  3. let teams = vec![String::from("Blue"), String::from("Yellow")];
  4. let initial_scores = vec![10, 50];
  5. // HashMap<_, _> 类型注解是必要的
  6. // 可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 HashMap 所包含的类型。
  7. let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
  8. let k = String::from("Blue");
  9. if let Some(v) = scores.get(&k) { //get 返回的是一个Option
  10. println!("v = {}", v);
  11. }
  12. let k = String::from("Yellow");
  13. let v = scores.get(&k);
  14. match v {
  15. Some(value) => println!("v = {}", value),
  16. None => println!("None"),
  17. }

更新

覆盖

  1. use std::collections::HashMap;
  2. let mut scores = HashMap::new();
  3. scores.insert(String::from("Blue"), 10);
  4. scores.insert(String::from("Blue"), 25);
  5. println!("{:?}", scores);

只在键没有对应值时插入

  1. use std::collections::HashMap;
  2. let mut scores = HashMap::new();
  3. scores.insert(String::from("Blue"), 10);
  4. scores.entry(String::from("Yellow")).or_insert(50);
  5. scores.entry(String::from("Blue")).or_insert(50);
  6. println!("{:?}", scores);

是否存在

  1. if map.contains_key(&nums[i]) {}
  2. temp = match map.get(&nums[i]) {
  3. Some(&x) => {
  4. 2
  5. },
  6. None => {
  7. map.insert(&nums[i], 1);
  8. 1
  9. }
  10. };

错误处理(Error Handling)

panic! 与不可恢复(unrecoverable)的错误

当执行panic! 这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出

Result 与可恢复(recoverable)的错误

使用 Result 类型来处理潜在的错误

  1. enum Result<T, E> {
  2. Ok(T),
  3. Err(E),
  4. }
  1. use std::fs::File;
  2. fn main() {
  3. let f = File::open("hello.txt");
  4. let f = match f {
  5. Ok(file) => file,
  6. Err(error) => {
  7. panic!("There was a problem opening the file: {:?}", error)
  8. },
  9. };
  10. }

匹配不同的错误

  1. use std::fs::File;
  2. use std::io::ErrorKind;
  3. fn main() {
  4. let f = File::open("hello.txt");
  5. let f = match f {
  6. Ok(file) => file,
  7. Err(error) => match error.kind() {
  8. ErrorKind::NotFound => match File::create("hello.txt") {
  9. Ok(fc) => fc,
  10. Err(e) => panic!("Tried to create file but there was a problem: {:?}", e),
  11. },
  12. other_error => panic!("There was a problem opening the file: {:?}", other_error),
  13. },
  14. };
  15. }
  1. use std::fs::File;
  2. use std::io::ErrorKind;
  3. fn main() {
  4. let f = File::open("hello.txt").map_err(|error| {
  5. if error.kind() == ErrorKind::NotFound {
  6. File::create("hello.txt").unwrap_or_else(|error| {
  7. panic!("Tried to create file but there was a problem: {:?}", error);
  8. })
  9. } else {
  10. panic!("There was a problem opening the file: {:?}", error);
  11. }
  12. });
  13. }

unwrap 和 expect

如果 Result 值是成员 Okunwrap 会返回 Ok 中的值。如果 Result 是成员 Errunwrap 会为我们调用 panic!

  1. use std::fs::File;
  2. fn main() {
  3. let f = File::open("hello.txt").unwrap();
  4. }

expect 与 unwrap 的使用方式一样:返回文件句柄或调用 panic! 宏。expect 用来调用 panic! 的错误信息将会作为参数传递给 expect ,而不像 unwrap 那样使用默认的 panic! 信息。

  1. use std::fs::File;
  2. fn main() {
  3. let f = File::open("hello.txt").expect("Failed to open hello.txt");
  4. }

A shortcut(快捷方式) for Propagating(传播的) Errors: the ? Operator

The ? operator can be used in functions that have a return type of Result, because it is defined to work in the same way as the match expression. The part of the match that requires a return type of Result is return Err(e), so the return type of the function has to be a Result to be compatible with this return.

  1. use std::io;
  2. use std::io::Read;
  3. use std::fs::File;
  4. fn read_username_from_file() -> Result<String, io::Error> {
  5. let mut s = String::new();
  6. // 使用 ? 如果没有报错继续向下执行,报错直接返回错误
  7. File::open("hello.txt")?.read_to_string(&mut s)?;
  8. Ok(s)
  9. }

泛型(Generic types)、traits 与生命周期(lifetimes)

泛型

在函数定义中使用泛型

当使用泛型定义函数时,我们在函数签名中通常为参数和返回值指定数据类型的位置放置泛型

  1. fn swap<T>(a: T, b: T) -> (T, T) {
  2. (b, a)
  3. }
  4. fn main() {
  5. println!("{:?}", swap(5.0f64, 7.5f64));
  6. println!("{:?}", swap("One", "Two"));
  7. }

在结构体中使用泛型

  1. struct Point<T, U> {
  2. x: T,
  3. y: U,
  4. }
  5. fn main() {
  6. let both_integer = Point { x: 5, y: 10 };
  7. let both_float = Point { x: 1.0, y: 4.0 };
  8. let integer_and_float = Point { x: 5, y: 4.0 };
  9. }

枚举中使用泛型

  1. enum Option<T> {
  2. Some(T),
  3. None,
  4. }
  5. enum Result<T, E> {
  6. Ok(T),
  7. Err(E),
  8. }

方法定义中的泛型

必须在 impl 后面声明 T,这样就可以在 Point<T> 上实现的方法中使用它。在 impl 之后声明泛型 T ,这样 Rust 就知道 Point 的尖括号中的类型是泛型而不是具体类型

  1. struct Point<T> {
  2. x: T,
  3. y: T,
  4. }
  5. impl<T> Point<T> {
  6. fn get_x(&self) -> &T {
  7. &self.x
  8. }
  9. fn get_y(&self) -> &T {
  10. &self.y
  11. }
  12. }
  13. struct Point2<T, U> {
  14. x: T,
  15. y: U,
  16. }
  17. impl<T, U> Point2<T, U> {
  18. fn creat_point<V, W>(self, other: Point2<V, W>) -> Point2<T, W> {
  19. Point2 {
  20. x: self.x,
  21. y: other.y,
  22. }
  23. }
  24. }
  25. fn main() {
  26. let p = Point{x:1, y: 2};
  27. println!("x = {}", p.get_x());
  28. println!("y = {}", p.get_y());
  29. let p = Point{x:1.1, y: 2.2};
  30. println!("x = {}", p.get_x());
  31. println!("y = {}", p.get_y());
  32. let p1 = Point2{x: 5, y: 1.1};
  33. let p2 = Point2{x: "hello", y: 'c'};
  34. let p3 = p1.creat_point(p2);
  35. println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
  36. }

trait 定义共享的行为

trait 用于定义与其它类型共享的功能,类似于其它语言中的接口。

  1. 可以通过 trait 以抽象的方式定义共享的行为
  2. 可以通过 trait bound 指定泛型是任何拥有特定行为的类型

    定义 trait

    ```rust pub trait GetInformation { fn get_name(&self) -> &String; fn get_age(&self) -> u32; }

trait SchoolName { fn get_school_name(&self) -> String { String::from(“HongXingSchool”) } }

  1. <a name="lKZdw"></a>
  2. ### 实现 trait
  3. ```rust
  4. pub struct Student {
  5. pub name: String,
  6. pub age: u32,
  7. }
  8. impl SchoolName for Student {}
  9. impl GetInformation for Student {
  10. fn get_name(&self) -> &String {
  11. &self.name
  12. }
  13. fn get_age(&self) -> u32 {
  14. self.age
  15. }
  16. }

trait 作为参数

  1. // 这个就是说传入的参数必须具有 GetInformation 这个trait
  2. fn print_information(item: impl GetInformation) {
  3. println!("name = {}", item.get_name());
  4. println!("age = {}", item.get_age());
  5. }

trait_bound

  1. trait GetName {
  2. fn get_name(&self) -> &String;
  3. }
  4. trait GetAge {
  5. fn get_age(&self) -> u32;
  6. }
  7. ////写法一:
  8. //fn print_information<T: GetName+GetAge>(item: T){ //使用trait bound的写法
  9. // println!("name = {}", item.get_name());
  10. // println!("age = {}", item.get_age());
  11. //}
  12. //写法二:
  13. fn print_information<T>(item: T)
  14. where T: GetName+GetAge
  15. { //使用trait bound的写法
  16. println!("name = {}", item.get_name());
  17. println!("age = {}", item.get_age());
  18. }
  1. pub struct Student {
  2. pub name: String,
  3. pub age: u32,
  4. }
  5. impl GetName for Student {
  6. fn get_name(&self) -> &String {
  7. &self.name
  8. }
  9. }
  10. impl GetAge for Student {
  11. fn get_age(&self) -> u32 {
  12. self.age
  13. }
  14. }
  15. fn main() {
  16. let s = Student{name: "xiaoming".to_string(), age: 10};
  17. print_information(s);
  18. }

对任何实现了特定trait的类型有条件的实现trait

  1. trait GetName {
  2. fn get_name(&self) -> &String;
  3. }
  4. trait PrintName {
  5. fn print_name(&self);
  6. }
  7. impl<T: GetName> PrintName for T {
  8. fn print_name(&self) {
  9. println!("name = {}", self.get_name());
  10. }
  11. }
  12. struct Student {
  13. name: String,
  14. }
  15. impl GetName for Student {
  16. fn get_name(&self) -> &String {
  17. &(self.name)
  18. }
  19. }
  20. fn main() {
  21. let s = Student{name: String::from("xiaohuang")};
  22. s.print_name();
  23. println!("Hello, world!");
  24. }

生命周期

引用保持有效的作用域

  1. Rust中每一个引用都有其生命周期,也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型可以推断一样。
  2. 生命周期的主要目标是避免悬垂引用
  3. Rust编译器使用借用检查器来检查生命周期是否有效
    1. fn main() {
    2. ////错误例子1
    3. //let r; //---------------------------+------------'a
    4. //{ // +
    5. // let x = 5; //-------+------'b |
    6. // r = &x; // | |
    7. //} //-------+ |
    8. //println!("r = {}", r); // |
    9. // // |
    10. //println!("Hello, world!"); // |
    11. // //---------------------------+
    12. //
    13. let r; //---------------------------+------------'a
    14. // +
    15. let x = 5; //-------+------'b |
    16. r = &x; // | |
    17. // | |
    18. println!("r = {}", r); // | |
    19. // | |
    20. println!("Hello, world!"); // | |
    21. }

    生命周期注解

    生命周期注解描述了多个引用生命周期相互的关系,而不影响其生命周期

  1. &i32 // 引用
  2. &'a i32 // 带有显式生命周期的引用
  3. &'a mut i32 // 带有显式生命周期的可变引用

函数中的生命周期

通过生命周期参数告诉 Rust 的是: longest 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致

  1. fn longest<'c>(x: &'c str, y: &'c str) -> &'c str {
  2. if x.len() > y.len() {
  3. x
  4. } else {
  5. y
  6. }
  7. }
  8. fn get_str<'a>(x: &'a str, y: &str) -> &'a str {
  9. x
  10. }
  11. fn main() {
  12. let s1 = String::from("abcde");
  13. let s2 = String::from("ab");
  14. let r = longest(s1.as_str(), s2.as_str());
  15. println!("r = {}", r);
  16. let ss = get_str(s1.as_str(), s2.as_str());
  17. println!("Hello, world!");
  18. }

结构体定义中的生命周期

结构体实例的不能比里面字段数据存在得更久

生命周期省略(Lifetime Elision)

(1)没有生命周期注解却能够编译,原因:早期的rust中必须显式的声明生命周期,后来rust团队将很明确的模式进行了注解的简化。
(2)遵守生命周期省略规则的情况下能明确变量的声明周期,则无需明确指定生命周期。函数或者方法的参数的生命周期称为输入生命周期,而返回值的生命周期称为输出生命周期。
(3)编译器采用三条规则判断引用何时不需要生命周期注解,当编译器检查完这三条规则后仍然不能计算出引用的生命周期,则会停止并生成错误。
(4)生命周期注解省略规则适用于fn定义以及impl块定义,如下:
a、每个引用的参数都有它自己的生命周期参数。例如如下:
一个引用参数的函数,其中有一个生命周期: fn foo<’a>(x: &’a i32)
两个引用参数的函数,则有两个生命周期 :fn foo<’a, ‘b>(x: &’a i32, y: &’b i32)
以此类推。
b、如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:
fn foo(x: &i32) -> &i32 等价于 fn foo<’a>(x: &’a i32) -> &’a i32
c、如果方法有多个输入生命周期参数,不过其中之一因为方法的缘故为&self或者&mut self,那么self的生命周期被赋予所有输出生命周期参数。例子在下面来看。
fn function(&self, x: &str, y: &str, ….) -> &str

  1. #[derive(Debug)]
  2. struct A<'a> {
  3. name: &'a str,
  4. }
  5. fn main() {
  6. let n = String::from("hello");
  7. let a = A{name: &n};
  8. println!("a = {:#?}", a);
  9. println!("Hello, world!");
  10. let s = get_a_str(&n);
  11. println!("s = {}", s);
  12. }
  13. //2、生命周期省略
  14. fn get_a_str(s: &str) -> &str {
  15. s
  16. }

方法定义中的生命周期注解

  1. struct StuA<'a> {
  2. name: &'a str,
  3. }
  4. impl<'b> StuA<'b> {
  5. fn do_something(&self) -> i32 {
  6. 3
  7. }
  8. fn do_something2(&self, s: &str) -> &str{
  9. //fn do_something2<'b>(&'b self, s: &str) -> &'b str{
  10. self.name
  11. }
  12. fn do_something3<'a>(&self, s: &'a str) -> &'a str{
  13. s
  14. }
  15. }
  16. fn main() {
  17. let s = String::from("hello");
  18. let a = StuA{name: &s};
  19. println!("{}", a.do_something());
  20. let s2 = String::from("hello");
  21. println!("{}", a.do_something2(&s2));
  22. println!("{}", a.do_something3(&s2));
  23. println!("Hello, world!");
  24. }

测试(writing automated tests)

  1. 设置任何所需的数据或状态
  2. 运行需要测试的代码
  3. 断言其结果是我们所期望的

    编写测试

    使用 #[test] 属性标明哪些函数是测试。 当使用 cargo test 命令运行测试时,Rust 会构建一个测试执行程序用来调用标记了 test 属性的函数,并报告每一个测试是通过还是失败。

  1. # fn main() {}
  2. # 测试模块的 #[cfg(test)] 注解告诉 Rust 只在执行 cargo test 时才编译和运行测试代码,
  3. # 而在运行 cargo build 时不这么做
  4. #[cfg(test)]
  5. mod tests {
  6. #[test]
  7. fn it_works() {
  8. assert_eq!(2 + 2, 4);
  9. }
  10. }

使用 assert! 宏来检查结果

  1. # fn main() {}
  2. #[cfg(test)]
  3. mod tests {
  4. use super::*;
  5. #[test]
  6. fn larger_can_hold_smaller() {
  7. let larger = Rectangle { length: 8, width: 7 };
  8. let smaller = Rectangle { length: 5, width: 1 };
  9. assert!(larger.can_hold(&smaller));
  10. }
  11. }

使用 assert_eq! 和 assert_ne! 宏来测试相等

  • assert_eq!:比较相等
  • assert_ne!:比较不相等

    使用 should_panic 检查 panic

    这个属性在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败。 可以给 should_panic 属性增加一个可选的 expected 参数。测试工具会确保错误信息中包含其提供的文本

  1. # fn main() {}
  2. # pub struct Guess {
  3. # value: i32,
  4. # }
  5. #
  6. // --snip--
  7. impl Guess {
  8. pub fn new(value: i32) -> Guess {
  9. if value < 1 {
  10. panic!("Guess value must be greater than or equal to 1, got {}.",
  11. value);
  12. } else if value > 100 {
  13. panic!("Guess value must be less than or equal to 100, got {}.",
  14. value);
  15. }
  16. Guess {
  17. value
  18. }
  19. }
  20. }
  21. #[cfg(test)]
  22. mod tests {
  23. use super::*;
  24. #[test]
  25. #[should_panic(expected = "Guess value must be less than or equal to 100")]
  26. fn greater_than_100() {
  27. Guess::new(200);
  28. }
  29. }

迭代器(Iterators)与闭包(Closures)

闭包

闭包是可以保存进变量或者作为参数传递给其它函数的匿名函数。闭包和函数不同的是,闭包允许捕获调用者作用域中的值。 闭包的定义以一对竖线(|)开始,在竖线中指定闭包的参数; 如果有多于一个参数,可以使用逗号分隔,比如 |param1, param2|

  1. fn main() {
  2. let use_closure = || {
  3. println!("This is a closure");
  4. };
  5. use_closure();
  6. println!("Hello, world!");
  7. //闭包定义会为每个参数和返回值类型推导一个具体的类型,但是不能推导两次
  8. let add_one_v2 = |x: u32| -> u32 { x + 1 };
  9. let add_one_v3 = |x| {x + 1};
  10. let add_one_v4 = |x| x+1;
  11. let a = add_one_v1(5);//use function
  12. let b = add_one_v2(5);
  13. let c = add_one_v3(5);
  14. let d = add_one_v4(5);
  15. println!("a = {}, b = {}, c = {}, d = {}", a, b, c, d);
  16. //不能推导两次的例子
  17. let example_closure = |x| x;
  18. let s = example_closure(String::from("hello"));
  19. println!("s = {}", s);
  20. //let n = example_closure(5);
  21. let n = example_closure(5.to_string());
  22. println!("n = {}", n);
  23. //捕捉环境中的变量
  24. let i = 1;
  25. let exe = |x| {x+i};
  26. let r = exe(5);
  27. println!("r = {}", r);
  28. }
  29. //语法格式
  30. fn add_one_v1(x: u32) -> u32 {
  31. x + 1
  32. }

捕获环境中的变量

闭包可以通过三种方式捕获其环境,它们对应函数的三种获取参数的方式,分别是获取所有权、可变借用、不可变借用。

  1. FnOnce消费从周围作用域捕获的变量,闭包周围的作用域被称为其环境。为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移进闭包。其名称的Once部分代表了闭包不能多次获取相同变量的所有权。
  2. FnMut获取可变的借用值,所以可以改变其环境
  3. Fn从其环境获取不可变的借用值。

当创建一个闭包时,rust会根据其如何使用环境中的变量来推断我们希望如何引用环境。由于所有闭包都可以被调用至少一次,因此所有闭包都实现了FnOnce。没有移动被捕获变量的所有权到闭包的闭包也实现了FnMut,而不需要对捕获的变量进行可变访问的闭包实现了Fn。

  1. fn main() {
  2. //let x = 4;
  3. //let equal_to_x = |z| z==x;
  4. //let y = 4;
  5. //assert!(equal_to_x(y));
  6. let x = vec![1, 2, 3];
  7. // 将 x 的所有权移入闭包中,后续不能使用
  8. let equal_to_x = move |z| {z==x};
  9. println!("x === {:?}", x);
  10. let y = vec![1, 2, 3];
  11. assert!(equal_to_x(y));
  12. }

迭代器(iterator

  1. 迭代器负责遍历序列中的每一项和决定序列何时结束的逻辑。
  2. 创建迭代器:迭代器是惰性的,意思就是在调用方法使用迭代器之前,不会有任何效果
  3. 每个迭代器都实现了iterator trait, iterator trait定义在标准库中 ```rust //trait Iterator { // type Item; // fn next(mut self) -> Option; //type Item和Self::Item这种用法叫做定义trait的关联类型 //} ////next是Iterator被要求实现的唯一的一个方法,next一次返回一个元素,当迭代器结束时候,返回None

fn main() { let v1 = vec![1, 2, 3]; let mut v1_iter = v1.iter(); //到目前为止,不会对v1产生任何影响 //for val in v1_iter { // println!(“val = {}”, val); //} if let Some(v) = v1_iter.next() { println!(“v = {}”, v);//1 } if let Some(v) = v1_iter.next() { println!(“v = {}”, v);//2 } if let Some(v) = v1_iter.next() { println!(“v = {}”, v);//3 } if let Some(v) = v1_iter.next() { println!(“v = {}”, v); } else { println!(“At end”); }

  1. //-----迭代可变引用-----
  2. let mut v2 = vec![1, 2, 3];
  3. let mut v2_iter = v2.iter_mut();
  4. if let Some(v) = v2_iter.next() {
  5. *v = 3;
  6. }
  7. println!("v2 = {:?}", v2);
  8. //-----消费适配器------
  9. let v1 = vec![1, 2, 3];
  10. let v1_iter = v1.iter();
  11. let total: i32 = v1_iter.sum();//调用消费适配器sum来求和
  12. println!("total = {}", total);
  13. //-----迭代适配器------
  14. println!("++++++++++++");
  15. let v1 = vec![1, 2, 3];
  16. println!("v1 = {:?}", v1);
  17. let v2: Vec<_> = v1.iter().map(|x| x+1).collect();
  18. println!("v2 = {:?}", v2);
  19. //------------------------
  20. println!("++++++++++++");
  21. let v1 = vec![1, 12, 3, 45];
  22. println!("v1 = {:?}", v1);
  23. let v2: Vec<_> = v1.into_iter().filter(|x| *x > 5).collect();
  24. println!("v2 = {:?}", v2);
  25. println!("Hello, world!");

}

  1. <a name="4Djcx"></a>
  2. # 智能指针(smart pointer)
  3. 1. 智能指针是一类数据结构,类似于指针,但拥有额外的元数据。特别是拥有一个引用计数(记录智能指针总共有多少个所有者,并且当没有所有者时清除数据)。
  4. 1. 普通引用和智能指针的一个额外区别:引用只是借用数据的指针,而智能指针则是拥有他们指向的数据
  5. 2. 智能指针通常使用结构体实现。区别于常规结构体的特征在于其实现了 Deref 和 Drop trait
  6. 1. Deref trait 允许智能指针结构体实例表现得像引用一样,这样可以编写既用于引用,又用于智能指针的代码
  7. 1. Drop trait 允许自定义当智能指针离开作用域时执行的代码
  8. 3. 几个标准库中的智能指针
  9. 1. Box<T>,用于在堆上分配
  10. 1. Rc<T>,一个引用计数类型,其数据可以有多个所有者
  11. 1. Ref<T> 和 RefMut<T>,通过 RefCell<T>访问,一个在运行时而不是在编译时执行借用规则的类型
  12. <a name="tlGmm"></a>
  13. ## Box<T>
  14. 1. 使用 Box<T> 将数据存储在堆上,当 Box 超出范围时,将调用析构函数销毁所有内存对象并释放内存
  15. 1. Box 适用场景
  16. 1. 递归类型和 trait 对象,Rust 需要在编译时知道一个类型占用多少空间,Box<T> 的大小是已知的
  17. 1. “大”的数据转移所有权,用 Box<T> 只需拷贝指针
  18. <a name="tWDJW"></a>
  19. ### 使用 Box<T> 将数据存储在堆上
  20. ```rust
  21. fn main()
  22. {
  23. let a = Box :: new(1);
  24. print!("value of a is : {}", a);
  25. }

微信截图_20210217101459.png

Box 在递归类型中的使用

  1. use List::{Cons, Nil};
  2. #[derive(Debug)]
  3. enum List<T> {
  4. Cons(T, Box<List<T>>),
  5. Nil,
  6. }
  7. fn main() {
  8. let recursive_list: List<i32> = Cons(1, Box::new(Cons(2, Box::new(Nil))));
  9. println!("{:?}", recursive_list); // 打印出:Cons(1, Cons(2, Nil))
  10. }

Box 在 trait 对象中的使用

Rust tries to be as explicit as possible whenever it allocates memory on the heap. So if your function returns a pointer-to-trait-on-heap in this way, you need to write the return type with the dyn keyword

  1. trait T {
  2. fn m(&self) -> u64;
  3. }
  4. struct S {
  5. i: u64
  6. }
  7. impl T for S {
  8. fn m(&self) -> u64 { self.i }
  9. }
  10. fn f(x: Box<dyn T>) {
  11. println!("{}", x.m())
  12. }
  13. fn main() {
  14. let s = S{i : 100};
  15. println!("{}", s.m());
  16. let b: Box<S> = Box::new(S{i: 100});
  17. f(b);
  18. }

Rc

默认 Rust 中,对一个资源,同一时刻,有且只有一个所有权拥有者。Rc 使用引用计数的方法,让程序在同一时刻,实现同一资源的多个所有权拥有者,多个拥有者共享资源。
Rc 用于同一线程内部,通过 use std::rc::Rc 来引入。它有以下几个特点:

  1. Rc 包装起来的类型对象,是 immutable 的,即 不可变的。即你无法修改 Rc<T> 中的 T 对象,只能读;
  2. 一旦最后一个拥有者消失,则资源会被自动回收,这个生命周期是在编译期就确定下来的;
  3. Rc 只能用于同一线程内部,不能用于线程之间的对象共享(不能跨线程传递);
  4. Rc 实际上是一个指针,它不影响包裹对象的方法调用形式(即不存在先解开包裹再调用值这一说)。 ```rust enum List { Cons(i32, Rc), Nil, }

use crate::List::{Cons, Nil}; use std::rc::Rc;

fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!(“count after creating a = {}”, Rc::strong_count(&a));

  1. let b = Cons(3, Rc::clone(&a));
  2. println!("count after bind to b, a count = {}", Rc::strong_count(&a));
  3. {
  4. let c = Cons(4, Rc::clone(&a));
  5. println!("count after bind to c, a count = {}", Rc::strong_count(&a));
  6. }
  7. println!("count at end, a = {}", Rc::strong_count(&a));
  8. println!("Hello, world!");

}

  1. <a name="tQfRI"></a>
  2. ## Cell, RefCell
  3. 提供了内部可变性<br />通常,我们要修改一个对象,必须
  4. 1. 成为它的拥有者,并且声明 `mut`;
  5. 1. 或 以 `&mut` 的形式,借用;
  6. 而通过 `Cell`, `RefCell`,我们可以在需要的时候,就可以修改里面的对象。而不受编译期静态借用规则束缚。
  7. <a name="JYhvq"></a>
  8. ### RefCell
  9. `RefCell` 实现了 `运行时借用`,这个借用是临时的。这意味着,编译器对 `RefCell` 中的内容,不会做静态借用检查。<br />`RefCell` 的特点:
  10. 1. 在不确定一个对象是否实现了 `Copy` 时,直接选 `RefCell`;
  11. 1. 如果被包裹对象,同时被可变借用了两次,则会导致线程崩溃。所以需要用户自行判断;
  12. 1. `RefCell` 只能用于线程内部,不能跨线程;
  13. 1. `RefCell` 常常与 `Rc` 配合使用(都是单线程内部使用);
  14. - .borrow():不可变借用被包裹值。同时可存在多个不可变借用
  15. - .borrow_mut():可变借用被包裹值。同时只能有一个可变借用。
  16. - .into_inner():取出包裹值。
  17. ```rust
  18. use std::collections::HashMap;
  19. use std::cell::RefCell;
  20. use std::rc::Rc;
  21. fn main() {
  22. let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
  23. shared_map.borrow_mut().insert("africa", 92388);
  24. shared_map.borrow_mut().insert("kyoto", 11837);
  25. shared_map.borrow_mut().insert("piccadilly", 11826);
  26. shared_map.borrow_mut().insert("marbles", 38);
  27. }

并发编程(concurrency)

using threads(线程) to run code simultaneously(同时地)

使用 spawn 创建新线程

  1. use std::thread;
  2. use std::time::Duration;
  3. fn main() {
  4. thread::spawn(|| {
  5. for i in 1..10 {
  6. println!("hi number {} from the spawned thread!", i);
  7. //调用强制线程停止执行一小段时间,这会允许其他不同的线程运行
  8. thread::sleep(Duration::from_millis(1));
  9. }
  10. });
  11. for i in 1..5 {
  12. println!("hi number {} from the main thread!", i);
  13. thread::sleep(Duration::from_millis(1));
  14. }
  15. }

使用 join 等待所有线程结束

  1. use std::thread;
  2. use std::time::Duration;
  3. fn main() {
  4. let handle = thread::spawn(|| {
  5. for i in 1..10 {
  6. println!("hi number {} from the spawned thread!", i);
  7. thread::sleep(Duration::from_millis(1));
  8. }
  9. });
  10. for i in 1..5 {
  11. println!("hi number {} from the main thread!", i);
  12. thread::sleep(Duration::from_millis(1));
  13. }
  14. handle.join().unwrap();
  15. }

线程与 move 闭包

其经常与 thread::spawn 一起使用,因为它允许我们在一个线程中使用另一个线程的数据
在参数列表前使用 move 关键字强制闭包获取其使用的环境值的所有权

  1. use std::thread;
  2. fn main() {
  3. let v = vec![1, 2, 3];
  4. let handle = thread::spawn(move || {
  5. println!("Here's a vector: {:?}", v);
  6. });
  7. handle.join().unwrap();
  8. }

using message passing(消息传递) to transfer data between threads

Do not communicate by sharing memory; instead, share memory by communicating.

Rust 中一个实现消息传递并发的主要工具是 通道channel

  • 发送者(transmitter)
  • 接收者(receiver) ```rust use std::thread; // 多个生产者,单个消费者(multiple producer, single consumer) use std::sync::mpsc;

fn main() { // tx 和 rx 作为 发送者(transmitter)和 接收者(receiver)的缩写 let (tx, rx) = mpsc::channel();

  1. thread::spawn(move || {
  2. let val = String::from("hi");
  3. // send 函数获取其参数的所有权并移动这个值归接收者所有
  4. tx.send(val).unwrap();
  5. });
  6. // recv 方法会阻塞主线程执行直到从通道中接收一个值
  7. let received = rx.recv().unwrap();
  8. println!("Got: {}", received);

}

  1. <a name="Z2frN"></a>
  2. ### 发送多个值并观察接收者的等待
  3. ```rust
  4. use std::thread;
  5. use std::sync::mpsc;
  6. use std::time::Duration;
  7. fn main() {
  8. let (tx, rx) = mpsc::channel();
  9. thread::spawn(move || {
  10. let vals = vec![
  11. String::from("hi"),
  12. String::from("from"),
  13. String::from("the"),
  14. String::from("thread"),
  15. ];
  16. for val in vals {
  17. tx.send(val).unwrap();
  18. thread::sleep(Duration::from_secs(1));
  19. }
  20. });
  21. for received in rx {
  22. println!("Got: {}", received);
  23. }
  24. }

通过克隆发送者来创建多个生产者

  1. # use std::thread;
  2. # use std::sync::mpsc;
  3. # use std::time::Duration;
  4. #
  5. # fn main() {
  6. // --snip--
  7. let (tx, rx) = mpsc::channel();
  8. let tx1 = mpsc::Sender::clone(&tx);
  9. thread::spawn(move || {
  10. let vals = vec![
  11. String::from("hi"),
  12. String::from("from"),
  13. String::from("the"),
  14. String::from("thread"),
  15. ];
  16. for val in vals {
  17. tx1.send(val).unwrap();
  18. thread::sleep(Duration::from_secs(1));
  19. }
  20. });
  21. thread::spawn(move || {
  22. let vals = vec![
  23. String::from("more"),
  24. String::from("messages"),
  25. String::from("for"),
  26. String::from("you"),
  27. ];
  28. for val in vals {
  29. tx.send(val).unwrap();
  30. thread::sleep(Duration::from_secs(1));
  31. }
  32. });
  33. for received in rx {
  34. println!("Got: {}", received);
  35. }
  36. // --snip--
  37. # }

shared-state

通过共享内存通讯

互斥器一次只允许一个线程访问数据

互斥器mutex)「mutual exclusion」,任意时刻,其只允许一个线程访问某些数据。

  1. 在使用数据之前尝试获取锁
  2. 处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁 ```rust use std::sync::Mutex;

fn main() { let m = Mutex::new(5);

  1. {
  2. let mut num = m.lock().unwrap();
  3. *num = 6;
  4. // Mutex 是一个智能指针,离开作用域时自动释放锁
  5. }
  6. println!("m = {:?}", m);

}

  1. <a name="AOany"></a>
  2. ### 通过 Arc 实现多线程和多所有权
  3. ```rust
  4. use std::sync::{Mutex, Arc};
  5. use std::thread;
  6. fn main() {
  7. let counter = Arc::new(Mutex::new(0));
  8. let mut handles = vec![];
  9. for _ in 0..10 {
  10. let counter = Arc::clone(&counter);
  11. let handle = thread::spawn(move || {
  12. let mut num = counter.lock().unwrap();
  13. *num += 1;
  14. });
  15. handles.push(handle);
  16. }
  17. for handle in handles {
  18. handle.join().unwrap();
  19. }
  20. println!("Result: {}", *counter.lock().unwrap());
  21. }

资料

  1. Rust 编程语言
  2. 令狐一冲
  3. rustPrimer