1 - Rust语法速查表

Rust语法速查表

语法汇总和速查表。

基本语法

类型

let a = 1;  							// 定义变量,默认不可变
let b: bool = true;				// 明确指定变量类型
let mut x = 5;						// 定义可变变量
const MAX_POINTS: u32 = 100_000;   // 定义常量
let i:i32 = _f as i32;		// 转数据类型
type Int = i32;  					// 用关键字 type 为i32类型创建别名Int

// Rust 的 never 类型( ! )用于表示永远不可能有返回值的计算类型。
#![feature(never_type)] 	
let x:! = {
    return 123
};

// 对整行进行注释
/* ..  对区块注释  */
/// 生成库文档,一般用于函数或者结构体的说明,置于说明对象的上方
//! 也生成库文档,一般用于说明整个模块的功能,置于模块文件的头部

let tup: (i32, f64, u8) = (500, 6.4, 1);			// 元组(tuple)
let (x, y, z) = tup;													// 模式匹配(pattern matching)来解构元组值
(1,)   // 当元组中只有一个元素时,需要加逗号,即 
()  // 空元组,
let arr: [i32; 3] = [1, 2, 3];  // 数组
assert_eq!((1..5), Range{ start: 1, end: 5 }); // 范围类型,左闭右开
assert_eq!((1..=5), RangeInclusive::new(1, 5)); // 范围类型,全闭
let arr: [i32; 5] = [1, 2, 3, 4, 5];						// 固定大小数组的切片
let arr = &mut [1, 2, 3];												// 可变数组的切片
let vec = vec![1, 2, 3];												// 使用 vec! 宏定义的动态数组的切片
let str_slice: &[&str] = &["one", "two", "three"];		// 字符串数组的切片
pub struct People {															// Named-Field Struct
    name: &'static str,
    gender: u32,
} // 注意这里没有分号
let alex = People::new("Alex", 1); // 用 :: 来调用new方法,默认不可变
struct Color(i32, i32, i32); // 注意这里要有分号! Tuple-Like Struct,字段没有名字,只有类型
let color = Color(0, 1, 2);		// 直接构造,不用new方法
struct Integer(u32); // 当元组结构体只有一个字段的时候,称为 New Type 模式
struct Empty;					// 等价于  struct Empty {},单元结构体是没有任何字段的结构体。

enum Number {		// 无参数枚举
    Zero,
    One,
    Two,
}
enum Color {		// 类C枚举
    Red = 0xff0000,
    Green = 0x00ff00,
    Blue = 0x0000ff,
}
enum IpAddr {		// 带参数枚举
    V4(u8, u8, u8, u8),
    V6(String),
}
let mut v1 = vec![]; 					// 用宏创建可变向量
let v2 = vec![0; 10];					// 用宏创建不可变向量
let mut v3 = Vec::new();			// 用 new 方法创建向量

let a = [1,2,3]; 
let b = &a;										// 引用操作符 &,不可变,本质上是一种非空指针
let mut c = vec![1,2,3];			// 要获取可变引用,必须先声明可变绑定
let d = &mut c;								// 通过 &mut 得到可变引用

let mut x = 10;
let ptr_x = &mut x as *mut i32;			// 可变原始指针 *&mut T
let y = Box::new(20);
let ptr_y = &*y as *const i32;			// 不可变原始指针 *const T


pub fn math(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32{			/// 将函数作为参数传递
    op(a, b)				/// 通过函数指针调用函数
}
fn true_maker() -> fn() -> bool { is_true }				/// 函数的返回值是另外一个函数

let box_point = Box::new(Point { x: 0.0, y: 0.0 });		// 智能指针


流程处理

let big_n = if n < 10 && n > -10 { // if 不带括号,真不适应
    10 * n
} else {
    n / 2
};

for n in 1..101 {} 			// for … in 循环
while n < 101 {}				// while 循环
loop { }								// loop 循环,相当于一个 while true,需要程序自己 break

2 - Rust基本语法

Rust的基本语法

2.1 - Rust的常量

Rust的常量

常量是绑定到一个名称的不允许改变的值。

常量与不可变变量的区别:

  • 不允许对常量使用 mut
  • 声明常量使用 const 关键字而不是 let,并且 必须 注明值的类型。
  • 常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值

备注:rust的常量是必须在编译时明确赋值的。

声明常量的例子:

const MAX_POINTS: u32 = 100_000;

2.2 - Rust的变量

Rust的变量

不可变变量

变量默认是不可改变的(immutable),这是Rust 提供的安全性和简单并发性来编写代码的众多方式之一。

let 关键字用于定义变量,默认定义的是不可变变量:

fn main() {
    // 可以通过类型推导得到变量类型,因此可以不制定变量类型
    let a = 1;
    // 也可以明确指定变量类型
    let b: bool = true;
}

Rust 编译器保证,如果声明一个值不会变,它就真的不会变。这意味着当阅读和编写代码时,不需要追踪一个值如何和在哪可能会被改变,从而使得代码易于推导。

可变变量

变量只是默认不可变;正如在第二章所做的那样,你可以在变量名之前加 mut 来使其可变。除了允许改变值之外,mut 向读者表明了其他代码将会改变这个变量值的意图。

fn main() {
    let mut x = 5;
    println!("The value of x is: {}", x);
    x = 6;
    println!("The value of x is: {}", x);
}

权衡使用

  • 使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快
  • 对于较小的数据结构,总是创建新实例,采用更偏向函数式的编程风格,可能会使代码更易理解,为可读性而牺牲性能或许是值得的

当多种类型均有可能时,必须增加类型注解:

let guess: u32 = "42".parse().expect("Not a number!");

变量隐藏

可以定义一个与之前变量同名的新变量,而新变量会 隐藏(Shadowing) 之前的变量。

Rustacean 们称之为第一个变量被第二个 隐藏 了,这意味着使用这个变量时会看到第二个值。可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏,如下所示:

fn main() {
    let x = 5;

    let x = x + 1;

    let x = x * 2;

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

当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,但复用这个名字:

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

2.3 - Rust的语句

Rust的语句

语句是指要执行的一些操作和产生副作用的表达式。

语句分为两种:

声明语句

用于声明各种语言项,包括声明变量,静态变量,常量,结构体,函数等,以及通过 extern 和 use 关键字引入包和模块等。

表达式语句

特指以分号结束的表达式。此类表达式求值结果将会被舍弃,并总是返回单元类型()

2.4 - Rust的表达式

Rust的表达式

表达式主要用于计算求值。

Rust 编译器在解析代码时:

  • 如果遇到分号,就会继续往后面执行
  • 如果遇到语句,就会执行语句
  • 如果遇到表达式,就会对表达式求值
  • 如果分号后面什么都没有,就会补上单元值()
  • 当遇到函数时,就会将函数体的花括号识别为块表达式。

块表达式

块表达式是由一对花括号和一系列表达式组成的,它总是返回块中最后一个表达式的值。

位置表达式

位置表达式(Place Expression)一般叫做左值,是表示内存位置的表达式,有以下几类:

  • 本地变量
  • 静态变量
  • 解引用 (* express)
  • 数组索引 (expr[expr])
  • 字段引用 (expr.field)
  • 位置表达式组合

通过位置表达式可以对某个数据单元的内存进行读写。位置表达式可以用于赋值。

值表达式

值表达式(Value Expression)一般叫做右值,值表达式引用了某个存储单元地址中的数据。它相当于数据,只能进行读操作。

从语义角度来说,位置表达式代表了持久性数据,值表达式代表了临时数据。位置表达式一般有持久的状态,值表达式要不是字面量,要不就是表达式求值过程中创建的临时值。

2.5 - Rust的注释

Rust的注释

Rust的注释有两种:

  1. 普通注释
    • // 对整行进行注释
    • /* .. */ 对区块注释
  2. 文档注释
    • /// 生成库文档,一般用于函数或者结构体的说明,置于说明对象的上方
    • //! 也生成库文档,一般用于说明整个模块的功能,置于模块文件的头部

示例:

/// # 文档注释: Sum函数
/// 该函数为求和函数
/// # usage:
///    assert_eq!(3, sum(1, 2));
fn sum(a: i32, b: i32) -> i32 {
    a + b
}
pub fn annotation() {
    // 这是单行注释的示例
    /*
         * 这是区块注释, 被包含的区域都会被注释
         * 你可以把/* 区块 */ 置于代码中的任何位置
    */
    /*
        注意上面区块注释中的*符号,纯粹是一种注释风格,
        实际并不需要
        */
    let x = 5 + /* 90 + */ 5;
    println!("Is `x` 10 or 100? x = {}", x);
    println!("2 + 3 = {}", sum(2, 3));
}

文档注释

文档注释支持 markdown !!

还支持对文档中的示例代码进行测试,可以用 rustdoc 工具生成 HTML 文档

2.6 - Rust的打印

Rust的打印

打印操作由std::fmt里面所定义的一系列宏来处理,包括:

  • format!:将格式化文本输出到 字符串(String)
  • print!:与 format!类似,但将文本输出到控制台
  • println!: 与 print!类似,但输出结果追加一个换行符

详细的使用说明见rust官方文档:

https://doc.rust-lang.org/1.0.0/std/fmt/index.html

2.7 - Rust的返回值

Rust的返回值

为什么使用返回值而不是异常

Why Rust uses Return Values for errors instead of Exceptions

我经常问自己,为什么Rust中的错误处理使用返回值而不是异常,在此进行解释,我引用在这里以备将来。

有些人需要在不允许使用 Exception 的地方使用Rust(因为展开表和清理代码太大)。这些人实际上包括所有浏览器供应商和游戏开发人员。此外, Exception 具有讨厌的代码生成权衡。您要么将它们设为零成本(如C ++,Obj-C和Swift编译器通常所做的那样),在这种情况下,在运行时抛出异常的代价非常高,或者使它们成为非零成本(如Java HotSpot和Go 6g/8g),在这种情况下,即使没有引发异常,您也会为每个 try 块(在Go中为defer)牺牲性能。对于使用RAII的语言,每个带有析构函数的堆栈对象都形成一个隐式try块,因此在实践中这是不切实际的。

零成本 Exception 的性能开销不是理论问题。我记得关于使用GCJ(使用零成本 Exception)进行编译时,Eclipse 需要花费30秒来启动的故事,因为它在启动时会引发数千个 Exception。

当您同时考虑错误和成功路径时,相对于 Exception,C处理错误的方法具有出色的性能和代码大小,这就是为什么系统代码非常喜欢它的原因。然而,它的人机工程学和安全性很差,Rust用Result来解决。Rust的方法形成了一种混合体,旨在实现C错误处理的性能,同时消除其陷阱。

3 - Rust的数据类型

Rust的数据类型

Rust 是 静态类型statically typed)语言,在编译时必须知道所有变量的类型。在 Rust 中,每一个值有明确的 数据类型data type),以便明确数据处理方式。

Rust 有两类数据类型子集:标量(scalar)和复合(compound)

标量类型

标量scalar)类型代表一个单独的值。

Rust 有四种基本的标量类型:

  1. 整型(integers)
  2. 浮点型(floating-point numbers)
  3. 布尔类型(Booleans)
  4. 字符(characters)

复合类型

复合类型Compound types)可以将多个值组合成一个类型。

Rust 有两个原生的复合类型:

  1. 元组(tuple)
  2. 数组(array)

3.1 - Rust的整型类型

Rust的整型类型

Rust 中的整型

Rust 内建的整数类型。在有符号列和无符号列中的每一个变体(例如,i16)都可以用来声明整数值的类型。

长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
arch isize usize

注意:isizeusize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。

整型字面值

可以使用表格中的任何一种形式编写数字字面值。注意除 byte 以外的所有数字字面值允许使用类型后缀,例如 57u8,同时也允许使用 _ 做为分隔符以方便读数,例如1_000

数字字面值 例子 说明
Decimal 98_222
Hex 0xff 16进制
Octal 0o77 8进制
Binary 0b1111_0000 2进制
Byte (u8 only) b'A' 字节字面量

那么该使用哪种类型的数字呢?如果拿不定主意,Rust 的默认类型通常就很好,数字类型默认是 i32:它通常是最快的,甚至在 64 位系统上也是。isizeusize 主要作为某些集合的索引。

整型溢出

“整型溢出”(“integer overflow” )在 Rust 中有一些有趣的规则:

  • 当在 debug 模式编译时,Rust 检查这类问题并使程序 panic

  • 在 release 构建中,Rust 不检测溢出,相反会进行一种被称为 “two’s complement wrapping” 的操作。

    简而言之,256 变成 0257 变成 1,依此类推。

    依赖溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping

3.2 - Rust的浮点型

Rust的浮点类型

Rust 有两个原生的 浮点数 f32f64,分别占 32 位和 64 位。

默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。

let num = 3.1415926f64;
assert_eq!(-3.14, -3.14f64);
assert_eq!(2., 2.0f64);
assert_eq!(2e4, 20000f64);

特殊值:

use std::f32::{INFINITY, NEG_INFINITY, NAN, MIN, MAX};
println!("{:?}", INFINITY);
println!("{:?}", NEG_INFINITY);
println!("{:?}", NAN);
println!("{:?}", MIN);
println!("{:?}", MAX);

打印结果为:

inf
-inf
NaN
-340282350000000000000000000000000000000.0
340282350000000000000000000000000000000.0

3.3 - Rust的布尔型

Rust的布尔类型

Rust 内置布尔类型

Rust 中的布尔类型使用 bool 表示,可以通过as操作将bool转为数字0和1,但是不支持从数字转为bool:

fn main() {
    let _t = true;

    // 显式指定类型注解
    let _f: bool = false;

    // 用 as 转成 int
    let i:i32 = _f as i32;

    print!("{}", i);
}

使用布尔值的主要场景是条件表达式,例如 if 表达式。

标准库

https://doc.rust-lang.org/std/primitive.bool.html

bool代表一个值,它只能是true或false。如果你把bool 转为整数,那么true将是1,false将是0。

bool实现了各种 trait ,如BitAnd、BitOr、Not等,这些特征允许我们使用&、|和 ! 来执行布尔运算。

assert! 是测试中的一个重要的宏,用于检查一个表达式是否返回真值。

let bool_val = true & false | false;
assert!(!bool_val);

3.4 - Rust的字符型

Rust的字符类型

使用单引号来定义字符类型。

Rust 的 char 类型代表了一个 Unicode 标量值(Unicode Scalar Value),每个字符占4个字节。

fn main() {
    let x = 'r';
    let x = 'Ú';
    // 支持转义
    println!("{}", '\'');
    println!("{}", '\\');
    println!("{}", '\n');
    println!("{}", '\r');
    println!("{}", '\t');
    // 用 ASCII 码表示字符
    assert_eq!('\x2A', '*');
    assert_eq!('\x25', '%');
    // 用 unicode 表示字符
    assert_eq!('\u{CA0}', 'ಠ');
    assert_eq!('\u{151}', 'ő');
    // 可以使用 as 操作符将字符转为数字类型
    assert_eq!('%' as i8, 37);
    assert_eq!('ಠ' as i8, -96); //该字符值的高位会被截断,最终得到-96
}

3.5 - Rust的never类型

Rust的never类型

Rust 的 never 类型( ! )用于表示永远不可能有返回值的计算类型。

Rust 是一个类型安全的语言,所以需要将没有返回值的情况(如线程退出)纳入类型管理。

#![feature(never_type)]
let x:! = {
    return 123
};

报错:

error[E0554]: #![feature] may not be used on the stable release channel
 --> src/main.rs:1:1
  |
1 | #![feature(never_type)]
  | ^^^^^^^^^^^^^^^^^^^^^^^

never 是试验特性,需要使用 nightly 版本。

3.6 - Rust的元组(Tuple)类型

Rust的元组(Tuple)类型

元组(tuple)是一种异构有限序列:

  • 异构 指元组内的元素可以是不同的类型
  • 有限 是指元组有固定长度

创建元组

使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的:

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

tup 变量绑定到整个元组上,因为元组是一个单独的复合元素。

元组取值

为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构元组值:

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

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

除了使用模式匹配解构外,也可以使用点号(.)后跟值的索引来直接访问它们:

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

跟大多数编程语言一样,元组的第一个索引值是 0。

特殊元组

当元组中只有一个元素时,需要加逗号,即 (1,)

空元组,`()`

3.7 - Rust的数组(Array)类型

Rust的数组(Array)类型

数组(array)与元组不同,数组中的每个元素的类型必须相同。数组的特点是:

  • 数组大小固定
  • 元素均为相同类型
  • 默认不可变

可以通过 let mut 关键字定义可变绑定的 mut_arr,但是也只能通过下标修改数组元素的值。

数组的类型签名为 [T; N]:

  • T 是泛型标记,代表数组中元素的具体类型
  • N 是数组长度,是一个 编译时常量,必须在编译时确认值,而且不可改变。

Rust 中数组的定义和使用方式:

// 声明数组,默认不可变
let arr: [i32; 3] = [1, 2, 3];
// 声明可变数组
let mut mut_arr = [1, 2, 3];
assert_eq!(1, mut_arr[0]);
// 通过下标修改可变数组元素的值
mut_arr[0] = 3;
assert_eq!(3, mut_arr[0]);
// 创建初始值为0大小为10的数组
let init_arr = [0; 10];
assert_eq!(0, init_arr[5]);
assert_eq!(10, init_arr.len());
// 下标越界
// error: index out of bounds: the len is 3 but the index is 5
println!("{:?}", arr[5]);

如果下标越界,rust会以 panic 的方式报错。

数组内存分配

数组是在栈(stack)而不是在堆(heap)上为数据分配内存空间。

对于原始固定长度数组,只有实现了 Copy trait 的类型才能作为其元素,也就是说,只有可以在栈上存放的元素才可以存放在该类型的数组中。

未来,rust将支持VLA(variable-length array) 数组,即可变长度数组。

3.8 - Rust的范围(Range)类型

Rust的范围(Range)类型

Rust 内置的范围类型,包括 左闭右开 和 全币 两种区间,分别是 std::ops::Range 和 std::ops::RangeInclusive 的实例:

// (1..5)是结构体std::ops::Range的一个实例
use std::ops::{Range, RangeInclusive};
assert_eq!((1..5), Range{ start: 1, end: 5 });
// (1..=5)是结构体std::ops::RangeInclusive的一个实例
assert_eq!((1..=5), RangeInclusive::new(1, 5));
// 自带的 sum 方法用于求和
assert_eq!(3+4+5, (3..6).sum());
assert_eq!(3+4+5+6, (3..=6).sum());
(3..6)

// 每个范围都是一个迭代器,可用for 循环打印范围内的元素
for i in (1..5) {
    println!("{}", i);
}
for i in (1..=5) {
    println!("{}", i);
}

3.9 - Rust的切片(Slice)类型

Rust的切片(Slice)类型

Slice 切片是对一个数组(包括固定大小数组和动态数组)的引用片段,可以安全访问数组的一部分,而不需要拷贝。

在底层,切片表示为一个指向数组起始位置的指针和数组长度。

// 固定大小数组的切片
let arr: [i32; 5] = [1, 2, 3, 4, 5];
assert_eq!(&arr, &[1,2,3,4,5]);
assert_eq!(&arr[1..], [2,3,4,5]);
assert_eq!((&arr).len(), 5);
assert_eq!((&arr).is_empty(), false);

// 可变数组的切片
let arr = &mut [1, 2, 3];
arr[1] = 7;
assert_eq!(arr, &[1, 7, 3]);

//使用 vec! 宏定义的动态数组的切片
let vec = vec![1, 2, 3];
assert_eq!(&vec[..], [1,2,3]);

// 字符串数组的切片
let str_slice: &[&str] = &["one", "two", "three"];
assert_eq!(str_slice, ["one", "two", "three"]);

3.10 - Rust的结构体(Struct)类型

Rust的结构体(Struct)类型

Rust 提供三种结构体:

  • Named-Field Struct
  • Tuple-Like Struct
  • Unit-Like Struct

Named-Field Struct

Named-Field 是最常见的。

#[derive(Debug, PartialEq)]
pub struct People {
    name: &'static str,
    gender: u32,
} // 注意这里没有分号

impl People {
    // new 方法的参数并没有 &self
    fn new(name: &'static str, gender: u32) -> Self {
        return People { name: name, gender: gender };
    }
    // 读方法,传递的是 &self 不可变引用
    fn name(&self) {
        println!("name: {:?}", self.name);
    }
    // 写方法,传递的是 &mut self 可变引用
    fn set_name(&mut self, name: &'static str) {
        self.name = name;
    }
    fn gender(&self) {
        let gender = if self.gender == 1 { "boy" } else { "girl" };
        println!("gender: {:?}", gender);
    }
}

fn main() {
    // 用 :: 来调用new方法,默认不可变
    let alex = People::new("Alex", 1);
    // 调用其他方法用 . 号,不用传递 &self
    // 为啥不直接把 &self 改成类型java的this语法呢?反正也不传递
    alex.name();
    alex.gender();
    // 也可以直接构建结构体,绕过new方法
    assert_eq!(alex, People { name: "Alex", gender: 1 });

    // 创建可变结构体
    let mut alice = People::new("Alice", 0);
    alice.name();
    alice.gender();
    assert_eq!(alice, People { name: "Alice", gender: 0 });
    // 就可以调用set方法了
    alice.set_name("Rose");
    alice.name();
    assert_eq!(alice, People { name: "Rose", gender: 0 });
}

结构体名字要用驼峰法。

Tuple-Like Struct

元组结构体像元组和结构体的混合体:字段没有名字,只有类型:

#[derive(Debug, PartialEq)]
struct Color(i32, i32, i32); // 注意这里要有分号!

fn main() {
    // 直接构造,不用new方法
    let color = Color(0, 1, 2);
    assert_eq!(color.0, 0);
    assert_eq!(color.1, 1);
    assert_eq!(color.2, 2);
}

使用 . 号按下标访问字段。

当元组结构体只有一个字段的时候,称为 New Type 模式:

#[derive(Debug, PartialEq)]
struct Integer(u32);

// 用关键字 type 为i32类型创建别名Int
type Int = i32;  

fn main() {
    let int = Integer(10);
    assert_eq!(int.0, 10);

    let int: Int = 10;
    assert_eq!(int, 10);
}

Unit-Like Struct

单元结构体是没有任何字段的结构体。

// 等价于  struct Empty {}
struct Empty;
let x = Empty;
println!("{:p}", &x);
let y = x;
println!("{:p}", &y as *const _);
let z = Empty;
println!("{:p}", &z as *const _);

// struct RangeFull;  // 标准库源码中RangeFull就是一个单元结构体
assert_eq!((..), std::ops::RangeFull); //  RangeFull就是(..),表示全范围

单元结构体和 new type 模式类似,也相当于定义了一个新的类型。

单元结构体一般用于特定场景,标准库源码中RangeFull就是一个单元结构体。

参考资料

视频:

3.11 - Rust的枚举(Enum)类型

Rust的枚举(Enum)类型

枚举用 enum 关键字定义,有三种类型。

无参数枚举体

enum Number {
    Zero,
    One,
    Two,
}
let a = Number::One;
match a {
    Number::Zero => println!("0"),
    Number::One => println!("1"),
    Number::Two => println!("2"),
}

类C枚举

enum Color {
    Red = 0xff0000,
    Green = 0x00ff00,
    Blue = 0x0000ff,
}
println!("roses are #{:06x}", Color::Red as i32);
println!("violets are #{:06x}", Color::Blue as i32);

带参数枚举

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}
let f: fn(u8, u8, u8, u8) -> IpAddr = IpAddr::V4;
let ff: fn(String) -> IpAddr = IpAddr::V6;
let home = IpAddr::V4(127, 0, 0, 1);

带参数枚举的值本质上属于函数指针类型:

  • fn(u8, u8, u8, u8) -> IpAddr
  • fn(String) -> IpAddr

参考资料

视频:

4 - Rust的字符串

Rust的字符串

出于内存安全的考虑,Rust将字符串分成两种类型:

  1. str字符串:固定长度,不可改变
  2. String字符串:可变长度

4.1 - Rust中的str字符串

Rust中的str字符串

Rust的原始字符串类型,也称为 字符串切片。

通常以不可变借用的形式存在,既 &str

str 由两部分组成:

  • 指向字符串序列的指针
  • 记录长度的值
use std::slice::from_raw_parts;
use std::str::from_utf8;

let truth: &'static str = "Rust是一门优雅的语言";
let ptr = truth.as_ptr();
let len = truth.len();
assert_eq!(28, len);

let s = unsafe {
    let slice = from_raw_parts(ptr, len);
    from_utf8(slice)
};
assert_eq!(s, Ok(truth));

Rust 中的字符串本质上是一段有效的 UTF-8 字符序列。

4.2 - Rust中的string字符串

Rust中的string字符串

字符串编码

// 将UTF-8序列转为字符串
let tao = std::str::from_utf8(&[0xE9u8, 0x81u8, 0x93u8]).unwrap();
assert_eq!("道", tao);

// 将16进制Unicode码位转为字符串
assert_eq!("道", String::from("\u{9053}")); 

let unicode_x = 0x9053;
let utf_x_hex = 0xe98193;
let utf_x_bin  = 0b111010011000000110010011;
println!("unicode_x: {:b}", unicode_x);
println!("utf_x_hex: {:b}", utf_x_hex);
println!("utf_x_bin: 0x{:x}", utf_x_bin);

5 - Rust的集合

Rust的集合

5.1 - Rust集合中的向量(Vec)

Rust集合中的向量(Vec)

向量(Vec) 也是一种数组,差别在于可动态增长。

// 用宏创建可变向量
let mut v1 = vec![];
v1.push(1);
v1.push(2);
v1.push(3);
assert_eq!(v1, [1,2,3]);
assert_eq!(v1[1], 2);

// 用宏创建不可变向量
let v2 = vec![0; 10];

// 用 new 方法创建
let mut v3 = Vec::new();
v3.push(4);
v3.push(5);
v3.push(6);

5.2 - Rust集合中的双端队列(VecDeque)

Rust集合中的双端队列(VecDeque)

Rust 中的 VecDeque 是基于可增长的 RingBuffer 算法实现的双端队列。

use std::collections::VecDeque;
let mut buf = VecDeque::new();

buf.push_front(1);
buf.push_front(2);
assert_eq!(buf.get(0), Some(&2));
assert_eq!(buf.get(1), Some(&1));

buf.push_back(3);
buf.push_back(4);
buf.push_back(5);

assert_eq!(buf.get(2), Some(&3));
assert_eq!(buf.get(3), Some(&4));
assert_eq!(buf.get(4), Some(&5));

5.3 - Rust集合中的链表(LinkedList)

Rust集合中的链表(LinkedList)

Rust 提供的链表是双向链表。

最好是使用 Vec 或者 VecDeque 类型,比链表更快。

use std::collections::LinkedList;
let mut list1 = LinkedList::new();

list1.push_back('a');

let mut list2 = LinkedList::new();
list2.push_back('b');
list2.push_back('c');

list1.append(&mut list2);
println!("{:?}", list1); // ['a', 'b', 'c']
println!("{:?}", list2); // []

list1.pop_front();
println!("{:?}", list1); // ['b', 'c']

list1.push_front('e');
println!("{:?}", list1); // ['e', 'b', 'c']

list2.push_front('f');
println!("{:?}", list2); // ['f']

5.4 - Rust集合中的映射(Map)

Rust集合中的映射(Map)

Rust 提供了两个 Key-Value 哈希映射表:

  • HashMap<K, V>:无序
  • BTreeMap<K, V>:有序

要求:Key 必须是可哈希的类型,Value 必须满足是在编译期已知大小的类型。

use std::collections::BTreeMap;
use std::collections::HashMap;
let mut hmap = HashMap::new();
let mut bmap = BTreeMap::new();
hmap.insert(3, "c");
hmap.insert(1, "a");
hmap.insert(2, "b");
hmap.insert(5, "e");
hmap.insert(4, "d");
bmap.insert(3, "c");
bmap.insert(2, "b");
bmap.insert(1, "a");
bmap.insert(5, "e");
bmap.insert(4, "d");
// 输出结果为:{1: "a", 2: "b", 3: "c", 5: "e", 4: "d"},但key的顺序是随机的,因为HashMap是无序的
println!("{:?}", hmap);
// 输出结果永远都是 {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"},因为BTreeMap是有序的
println!("{:?}", bmap);

5.5 - Rust集合中的集合(Set)

Rust集合中的集合(Set)

Rust 提供了两个哈希集合:

  • HashSet<K>:无序,等同于 HashMap<K, ()>,值为空元组的特定类型
  • BTreeSet<K>:有序,等同于 BTreeMap<K, ()>,值为空元组的特定类型

特性如下:

  • 集合中的元素是唯一的
  • 集合中的元素是可哈希的类型
use std::collections::HashSet;
use std::collections::BTreeSet;
let mut hbooks = HashSet::new();
let mut bbooks = BTreeSet::new();
// 插入数据
hbooks.insert(2);
hbooks.insert(1);
hbooks.insert(2);
// 判断元素是否存在,contains方法和HashMap中的一样
if !hbooks.contains(&1) {
}
println!("{:?}", hbooks);
bbooks.insert(1);

bbooks.insert(2);
bbooks.insert(3);
println!("{:?}", bbooks); // 输出固定为 {1, 2, 3} ,因为是有序

5.6 - Rust集合中的优先队列(BinaryHeap)

Rust集合中的优先队列(BinaryHeap)

Rust 提供的优先队列是基于二叉最大堆(Binary Heap)实现的。

use std::collections::BinaryHeap;
let mut heap = BinaryHeap::new();
assert_eq!(heap.peek(), None);
heap.push(93);
heap.push(80);
heap.push(48);
heap.push(53);
heap.push(72);
heap.push(30);
heap.push(18);
heap.push(36);
heap.push(15);
heap.push(35);
heap.push(45);
assert_eq!(heap.peek(), Some(&93));
println!("{:?}", heap);  // [93, 80, 48, 53, 72, 30, 18, 36, 15, 35, 45]

6 - Rust的指针

Rust的指针

6.1 - Rust的指针概述

Rust的指针概述

Rust 中将可以表示内存地址的类型成为 指针

Rust提供了多种类型的指针:

  • 引用(Reference)
  • 原生指针(Raw Pointer)
  • 函数指针(fn Pointer)
  • 智能指针(Smart Pointer)

Rust 可以划分为 Safe Rust 和 Unsafe Rust 两部分。

Safe Rust

引用主要应用于 Safe Rust。

在Safe Rust 中,编译器会对引用进行借用检查,以保证内存安全和类型安全。

Unsafe Rust

原生引用主要用于 Unsafe Rust。

原生引用不在 Safe Rust 的控制范围内,需要编程人员自己保证安全。

6.2 - Rust的引用(Reference)

Rust的引用(Reference)

Rust 提供引用操作符 &,可以直接获取表达式的存储单元地址,即内存地址。

引用本质上是一种非空指针。

let a = [1,2,3];
let b = &a;
println!("{:p}", b); // 0x7ffcbc067704

// 要获取可变引用,必须先声明可变绑定
let mut c = vec![1,2,3];
// 通过 &mut 得到可变引用
let d = &mut c;
d.push(4);
println!("{:?}", d); // [1, 2, 3, 4]

let e = &42;
assert_eq!(42, *e);

从语义上说,不管是 &a 还是 &mut c,都是对原有变量的所有权的借用,所以引用也被称为借用。

6.3 - Rust的原始引用(Raw Reference)

Rust的原始引用(Raw Reference)

Rust 支持两种原始引用:

  • 不可变原始指针 *const T
  • 可变原始指针 *&mut T

用 as 操作符可以将引用转为原始指针:

let mut x = 10;
let ptr_x = &mut x as *mut i32;
let y = Box::new(20);
let ptr_y = &*y as *const i32;

// 原生指针操作要放在unsafe中执行
unsafe {
    *ptr_x += *ptr_y;
}
assert_eq!(x, 30);

6.4 - Rust的函数指针(Fn Pointer)

Rust的函数指针(Fn Pointer)

函数在Rust中是一等公民,函数自身就可以作为函数的参数和返回值使用。

函数作为参数

​```
/// 将函数作为参数传递
pub fn math(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32{
    /// 通过函数指针调用函数
    op(a, b)
}
fn sum(a: i32, b: i32) -> i32 {
    a + b
}
fn product(a: i32, b: i32) -> i32 {
    a * b
}

let a = 2;
let b = 3;
assert_eq!(math(sum, a, b), 5);
assert_eq!(math(product, a, b), 6);
​```

函数作为返回值

fn is_true() -> bool { true }
/// 函数的返回值是另外一个函数
fn true_maker() -> fn() -> bool { is_true }
/// 通过函数指针调用函数
assert_eq!(true_maker()(), true);
​```

6.5 - Rust的智能指针(Smart Pointer)

Rust的智能指针(Smart Pointer)

智能指针 (Smart Pointer) 源自c++,Rust 引入后成为 Rust 语言中最重要的一种数据结构。

功能介绍

Rust 中的值默认被分配到 栈内存。可以通过 Box<T> 将值装箱(在堆内存中分配)。

  • Box<T> 是指向类型为T的堆内存分配值的智能指针。
  • Box<T> 超出作用域范围时,将调用其析构函数,销毁内部对象,并自动释放内存。
  • 可以通过解引用操作符来获取Box<T>中的T

Box<T> 的行为像引用,并可以自动释放内存,所以称为智能指针。

Box<T>类型

Rust 中提供了很多智能指针类型。

#[derive(Debug, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}
let box_point = Box::new(Point { x: 0.0, y: 0.0 });
let unboxed_point: Point = *box_point;
assert_eq!(unboxed_point, Point { x: 0.0, y: 0.0 });

7 - Rust的流程控制

Rust的流程控制

Rust 中不叫流程控制语句,而是叫做流程控制表达式。

这是关键:流程表达式是可以作为右值赋值的

7.1 - Rust的if条件表达式

Rust的if条件表达式

表达式一定会有值,所以 if 条件表达式的分支必须返回同一个类型的值。

let n = 13;
// if 表达式可以用来赋值
let big_n = if n < 10 && n > -10 {
    // 分支必须返回同一个类型的值
    10 * n
} else {
    // 自动截取
    n / 2
};
assert_eq!(big_n, 6);

7.2 - Rust的for ... in循环表达式

Rust的for … in循环表达式

循环表达式

Rust 有三种循环表达式:while 、loop 和 for … in 表达式。

for … in 循环:

for n in 1..101 {
    if n % 15 == 0 {
        println!("fizzbuzz");
    } else if n % 3 == 0 {
        println!("fizz");
    } else if n % 5 == 0 {
        println!("buzz");
    } else {
        println!("{}", n);
    }
}

注意 for … in 后面是一个 Rang 类型,左闭右开,所以这个循环的最后一个n值是100。

7.3 - Rust的loop循环表达式

Rust的loop循环表达式

loop 循环,相当于一个 while true,需要程序自己 break:

let mut n = 1;
loop {
    if n > 101 { break; }
    if n % 15 == 0 {
        println!("fizzbuzz");
    } else if n % 3 == 0 {
        println!("fizz");
    } else if n % 5 == 0 {
        println!("buzz");
    } else {
        println!("{}", n);
    }
    n += 1;
}

强调:当使用无限循环时,务必使用 loop,避免使用 while true。

7.4 - Rust的while循环表达式

Rust的while循环表达式

while 循环,没啥特别:

let mut n = 1;
while n < 101 {
    if n % 15 == 0 {
        println!("fizzbuzz");
    } else if n % 3 == 0 {
        println!("fizz");
    } else if n % 5 == 0 {
        println!("buzz");
    } else {
        println!("{}", n);
    }
    n += 1;
}

7.5 - Rust的match表达式

Rust的match表达式

match用于匹配各种情况,类似其他语言的 switch 或 case。

在 Rust 语言中,match 分支使用 模式匹配 (pattern matching)技术,match分支:

  • 左边是模式:
    • 不同分支可以是不同的模式
    • 必须穷尽每一种可能,所以通常最后使用通配符 _
  • 右边是执行代码
    • 同样所有分支必须返回同一个值
let number = 42;
match number {
    // 模式为单个值
    0 => println!("Origin"),
    // 模式为Range
    1...3 => println!("All"),
    // 模式为 多个值
    | 5 | 7 | 13  => println!("Bad Luck"),
    // 绑定模式,将模式中的值绑定给一个变量,供右边执行代码使用
    n @ 42 => println!("Answer is {}", n),
    // _ 通配符处理剩余情况
    _ => println!("Common"),
}

match语句可以直接用来赋值,代码比较简练:

let boolean = true;
let binary = match boolean {
    false => 0,
    true => 1,
};

7.6 - Rust的 if let 表达式

Rust的 if let 表达式

if let 表达式用来在某些场合替代 match 表达式.

let boolean = true;
let mut binary = 0;
// if let 左侧为模式,右侧为匹配的值
if let true = boolean {
    binary = 1;
}
assert_eq!(binary, 1);

备注:这个例子看不出 if let 的价值所在。

7.7 - Rust的 while let 表达式

Rust的 while let 表达式

while let 可以简化代码,如这个loop:

let mut v = vec![1,2,3,4,5];
loop {
    match v.pop() {
        Some(x) => println!("{}", x),
        None => break,
    }
}

可以改写为:

let mut v = vec![1,2,3,4,5];
while let Some(x) = v.pop() {
    println!("{}", x);
}