Rust语法
- 1: Rust语法速查表
- 2: Rust基本语法
- 3: Rust的数据类型
- 3.1: Rust的整型类型
- 3.2: Rust的浮点型
- 3.3: Rust的布尔型
- 3.4: Rust的字符型
- 3.5: Rust的never类型
- 3.6: Rust的元组(Tuple)类型
- 3.7: Rust的数组(Array)类型
- 3.8: Rust的范围(Range)类型
- 3.9: Rust的切片(Slice)类型
- 3.10: Rust的结构体(Struct)类型
- 3.11: Rust的枚举(Enum)类型
- 4: Rust的字符串
- 4.1: Rust中的str字符串
- 4.2: Rust中的string字符串
- 5: Rust的集合
- 5.1: Rust集合中的向量(Vec)
- 5.2: Rust集合中的双端队列(VecDeque)
- 5.3: Rust集合中的链表(LinkedList)
- 5.4: Rust集合中的映射(Map)
- 5.5: Rust集合中的集合(Set)
- 5.6: Rust集合中的优先队列(BinaryHeap)
- 6: Rust的指针
- 6.1: Rust的指针概述
- 6.2: Rust的引用(Reference)
- 6.3: Rust的原始引用(Raw Reference)
- 6.4: Rust的函数指针(Fn Pointer)
- 6.5: Rust的智能指针(Smart Pointer)
- 7: Rust的流程控制
- 7.1: Rust的if条件表达式
- 7.2: Rust的for ... in循环表达式
- 7.3: Rust的loop循环表达式
- 7.4: Rust的while循环表达式
- 7.5: Rust的match表达式
- 7.6: Rust的 if let 表达式
- 7.7: Rust的 while let 表达式
1 - 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基本语法
2.1 - Rust的常量
常量是绑定到一个名称的不允许改变的值。
常量与不可变变量的区别:
- 不允许对常量使用
mut
- 声明常量使用
const
关键字而不是let
,并且 必须 注明值的类型。 - 常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值
备注:rust的常量是必须在编译时明确赋值的。
声明常量的例子:
const MAX_POINTS: u32 = 100_000;
2.2 - 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的语句
语句是指要执行的一些操作和产生副作用的表达式。
语句分为两种:
声明语句
用于声明各种语言项,包括声明变量,静态变量,常量,结构体,函数等,以及通过 extern 和 use 关键字引入包和模块等。
表达式语句
特指以分号结束的表达式。此类表达式求值结果将会被舍弃,并总是返回单元类型()
。
2.4 - Rust的表达式
表达式主要用于计算求值。
Rust 编译器在解析代码时:
- 如果遇到分号,就会继续往后面执行
- 如果遇到语句,就会执行语句
- 如果遇到表达式,就会对表达式求值
- 如果分号后面什么都没有,就会补上单元值
()
- 当遇到函数时,就会将函数体的花括号识别为块表达式。
块表达式
块表达式是由一对花括号和一系列表达式组成的,它总是返回块中最后一个表达式的值。
位置表达式
位置表达式(Place Expression)一般叫做左值,是表示内存位置的表达式,有以下几类:
- 本地变量
- 静态变量
- 解引用 (* express)
- 数组索引 (expr[expr])
- 字段引用 (expr.field)
- 位置表达式组合
通过位置表达式可以对某个数据单元的内存进行读写。位置表达式可以用于赋值。
值表达式
值表达式(Value Expression)一般叫做右值,值表达式引用了某个存储单元地址中的数据。它相当于数据,只能进行读操作。
从语义角度来说,位置表达式代表了持久性数据,值表达式代表了临时数据。位置表达式一般有持久的状态,值表达式要不是字面量,要不就是表达式求值过程中创建的临时值。
2.5 - Rust的注释
Rust的注释有两种:
- 普通注释
//
对整行进行注释/* .. */
对区块注释
- 文档注释
///
生成库文档,一般用于函数或者结构体的说明,置于说明对象的上方//!
也生成库文档,一般用于说明整个模块的功能,置于模块文件的头部
示例:
/// # 文档注释: 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的打印
打印操作由std::fmt
里面所定义的一系列宏来处理,包括:
format!
:将格式化文本输出到字符串
(String)print!
:与format!
类似,但将文本输出到控制台println!
: 与print!
类似,但输出结果追加一个换行符
详细的使用说明见rust官方文档:
2.7 - 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 是 静态类型(statically typed)语言,在编译时必须知道所有变量的类型。在 Rust 中,每一个值有明确的 数据类型(data type),以便明确数据处理方式。
Rust 有两类数据类型子集:标量(scalar)和复合(compound)
标量类型
标量(scalar)类型代表一个单独的值。
Rust 有四种基本的标量类型:
- 整型(integers)
- 浮点型(floating-point numbers)
- 布尔类型(Booleans)
- 字符(characters)
复合类型
复合类型(Compound types)可以将多个值组合成一个类型。
Rust 有两个原生的复合类型:
- 元组(tuple)
- 数组(array)
3.1 - Rust的整型类型
Rust 中的整型
Rust 内建的整数类型。在有符号列和无符号列中的每一个变体(例如,i16
)都可以用来声明整数值的类型。
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 |
u8 |
16-bit | i16 |
u16 |
32-bit | i32 |
u32 |
64-bit | i64 |
u64 |
arch | isize |
usize |
注意:isize
和 usize
类型依赖运行程序的计算机架构: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 位系统上也是。isize
或 usize
主要作为某些集合的索引。
整型溢出
“整型溢出”(“integer overflow” )在 Rust 中有一些有趣的规则:
-
当在 debug 模式编译时,Rust 检查这类问题并使程序 panic
-
在 release 构建中,Rust 不检测溢出,相反会进行一种被称为 “two’s complement wrapping” 的操作。
简而言之,
256
变成0
,257
变成1
,依此类推。依赖溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,
Wrapping
。
3.2 - Rust的浮点型
Rust 有两个原生的 浮点数 f32
和 f64
,分别占 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 中的布尔类型使用 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 的 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 是一个类型安全的语言,所以需要将没有返回值的情况(如线程退出)纳入类型管理。
#![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)类型
元组(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)类型
数组(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 内置的范围类型,包括 左闭右开 和 全币 两种区间,分别是 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)类型
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 提供三种结构体:
- 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)类型
枚举用 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将字符串分成两种类型:
- str字符串:固定长度,不可改变
- String字符串:可变长度
4.1 - 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字符串
字符串编码
// 将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的集合
5.1 - 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 是基于可增长的 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 提供的链表是双向链表。
最好是使用 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 提供了两个 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 提供了两个哈希集合:
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 提供的优先队列是基于二叉最大堆(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的指针
6.1 - 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 提供引用操作符 &
,可以直接获取表达式的存储单元地址,即内存地址。
引用本质上是一种非空指针。
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 支持两种原始引用:
- 不可变原始指针
*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中是一等公民,函数自身就可以作为函数的参数和返回值使用。
函数作为参数
```
/// 将函数作为参数传递
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)
智能指针 (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 中不叫流程控制语句,而是叫做流程控制表达式。
这是关键:流程表达式是可以作为右值赋值的
7.1 - 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 有三种循环表达式: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循环表达式
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循环表达式
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表达式
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 表达式
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 表达式
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);
}