Rust 语言学习笔记(二)

再继续快速学习一下 Rust 的以下几个知识点,就可以开始着手做点小工具了

  1. 基本数据类型
  2. 复合数据类型
  3. 基本的流程控制
Rust 设计为有效使用内存考虑的,它提供了非常细力度的数据类型,如整数分为有无符号,宽度从 8 位到 128 位,分别表示为 i8, u8, u128 等。浮点数有 f32 和 f64,以及 bool 和 range 类型。

元组

元组和 Python 的元组用法类似,Immutable, 可混合类型
1let tup1 = (10, 7.2, 'a');
2let tup2 = (100, );     // 一个元素时,和 Python 一样,后面附加逗号,否则视括号可选
3let tup3: (i8, f32, boo) = (-10, 7.7, false); // 类型要一一对应
4let tup4: () = ();     // 声明一个空元组,元组是不可变的,所以没什么意义
5
6let (x, y, z) = tup1;   // 元组的拆解
7
8println!("{}", tup1.0); // 访问用 .index 方式访问

数组

数组类型中的元素类型相同,表示为  [T; n], T 为类型,n 为元素个数
1let arr1 = [1, 2, 3];
2let arr2: [i8; 2] = [2, 3];
3let arr3 = [1; 3]; // 类型位置为值表示类型自动推断,所有元素的初始值,长度为 3
4let [a, b, c] = arr1;
5println!("{}", arr1[0]);
6let arr4: [[i32; 2]; 3] = [[1, 2], [3, 4], [5, 6]]; // 推广到多维数组
7dbg!(arr4);

切片(Slice)类型

相同类型但不同长度的数组是不同的类型,如 [T; 1], [T; 2], [T; 3], 但它们的 Slice 类型都表示为 &[T]. Rust 的切片与 Python 同名概念类似
1let arr = [1, 2, 3];
2let slice1 = &arr[0..2]; // &[i32]
3let s = String::from("hello");
4let slice2 = &s[..];    // &str
5let s1 = "world";
6let slice3 = &s1[2..];  // &str
&[i32], &str 是 slice1, slice2, slice3 的推断类型,Slice 是一个视图,编译期无法确定长度。

Rust 不同宽度的类型之间不会自动转换的
1let mut a: i32 = 13;
2// a = 10i8;   // 类型不匹配,虽然 a 足够宽
3a = 10i8 as i32 // 必须转型
不同宽度类型之间比较大小也不允许。

结构体类型

Rust 仍然延续了 C 的结构体
 1struct Student {
 2    name: &'static str,
 3    score: i32,  // 最后豆号可省略
 4}
 5
 6let mut student = Student {  // 结构体默认也是不可变的
 7    score: 59,
 8    name: "Scott",
 9};
10student.score = 60;
11println!("name: {}, score: {}", student.name, student.score);
12
13let name = "Tiger";
14let student2 = Student {
15    name,             
16    ..student
17};
18println!("name: {}, score: {}", student2.name, student2.score);
19
20struct Color(i32, i32, i32); // 元组结构体,只有类型没有名称
21let black = Color(0, 0, 0);
22println!("Red: {}", black.0); // 以元组方式访问元素
23
24struct Solution; // Unit 结构体, 只能用作标识

枚举类型

1#[derive(Debug)]   // 支持 {:?} 打印
2enum Color {Red, Yellow, Blue};
3
4let color = Color::Red;
5println!("{:?}", color)
struct 和 enum 都支持用 impl 块给它们附加方法。

有有参数的枚举
 1#[derive(Debug, PartialEq)]
 2enum Color {
 3    Red(&'static str),
 4    Yellow(&'static str),
 5    Blue(&'static str),
 6}
 7
 8fn main() {
 9    let y1 = Color::Yellow("黄");
10    let y2 = Color::Yellow("Y");
11    let y3 = Color::Yellow("黄");
12    println!("{:?}, {:?}, y1==y2?: {}, y1==y3?: {}", y1, y2, y1 == y2, y1 == y3);
13
14    if let Color::Yellow(x) = y1 {
15        println!("{}", x)
16    }
17}

输出
Yellow("黄"), Yellow("Y"), y1==y2?: false, y1==y3?: true 黄
黄可以黄的不一样,Enum 可以这样用于模式匹配

Rust 的标准库 std::collections 提供了 4 种支持泛型的容器类型, Vec<T>, VecDeque<T>, LinkedList<T>, HashMap<K, V>, BTreeMap<K, V>, HashSet<T>, BTreeSet<T>, Binaryheap<T>。
1let mut v: Vec<i32> = Vec::new();
2v.push(1);

Vec 的初始容量是 0, 不指定 mut 的话,对  v 的内容都不可修改。调用 v.push() 往其中添加元素过程中,v.len 与 v.buf.cap 之间的关系
  1. 0 -> 0
  2. 1 ~4 -> 4
  3. 5 ~ 8 -> 8
  4. 9 ~ 16 -> 16
  5. 17 ~ 32 -> 32

由此可见,至少从目前的 Vec 实现 (Rust 1.75), Vec 只在容量不足时增长,并且每次翻倍。这种实现效率会有两个问题

1) 扩容太频繁, 消耗资源, Vec 新创建一个 2 倍容量的底层数组,并将现有元素移新缓冲中去

2) 元素越多, v.len 与 v.buf.cap 差值越大,也就造成 Buffer 中浪费的空间就越大. 比如元素数量为 4096 时,再添加一个元素就扩容到了 8092,这时实际使用 4097, 空闲 4095

所以使用 Vec 最好预先估算好容量,或用 LinkedList,如用下面的方式声明 Vec
1let mut v: Vec<i32> = Vec::with_capacity(10);

可用 vec! 宏来声明 Vec
1let mut v1: Vec&lt;i32&gt; = vec![]; // v2.len 和 v.buf.cap 都为 0
2let mut v2 = vec![1, 2, 3];  // v2.len 和 v.buf.cap 都为 3,推断类型为 int32
3let mut v3 = vec![0; 10];    // v2.len 和 v.buf.cap 都为 10,并且全部填充为 0

Vec 的基本操作有 v[index], get(index), push(value), pop(), remove(index) 等。get(index) 得到的是一个 Option[&i32): Some(value) 或 None, 和 Scala 相似的 Option

其他的类型不具体说明,用到时再查 API,单独说一个 HashMap 的 map.insert(key, value) 和 entry(key).or_insert(value) 操作
  1. map.insert(key, value): 不存在 key, 插入新的键值对,已存在 key 则更新对应值, 总是覆盖旧值
  2. entry(key).or_insert(value):不存在 key, 插入新的键值对,已存在 key 则不作任何操作, 不覆盖旧值
字符串本质上是字符序列,分不可变的 str 和可变的 String

字符串(str 和 String)

&str 的创建
1let s1 = "Hello, World";
2let str = String::from("Hello");
3let s2 = str.as_str();
String 本质上是一个字段为 Vec<u8> 的结构体,声明为
1pub struct String {
2    vec: Vec<u8>,
3}
它是可变的,字符串内容在堆中,由字符串长度读取到实际内容。创建 String 的方式有
1let mut s1 = String::new();
2let s2 = String::from("Hello World!");
3let str = "Hello, Rult!";
4let s3 = str.to_string();
String 的 vec buf 容量也是成倍的增长, 只是 buf 为零时第一次增长不同, 例如下面的代码
1let mut s = String::new();
2let x = 8;
3s.push_str("a".repeat(x).as_str());
如果 x <=8, push_str 之后, s.vec.buf 缓冲大小为 8, x > 8 的话,v.vec.buf 缓冲大小就是 x。String 还可用 "+" 连接 &str 字符串,它会返回新的 String
1let s1 = String::from("Hello");
2let s2 = String::from(" Rust");
3let s3 = " World";
4let mut s = s1 + &s2 + s2.as_str() + s3;
format! 宏可连接 str 和 Strign
1let s = format!("{}-{}-{}", s1, s3, "Yet")
Rust 的 String 不能直接使用索引(s[idx]方式) 来访问其中的字符, 字符串迭代分 bytes() 和 chars() 两种方式。bytes() 迭代出来的是字节的整数值. len() 获取的是以字节为单位的长度。更多 String 的操作就查 API 吧。

数字字面的类型用后缀来表示,所 2u8, 1.2f32. Rust 不支持 ++, 和 -- 操作。Rust 的各种算术运算,关系运算,逻辑运算,位运算和 C 和 Java 是一样的,运算优先级也没必要去记,知道乘除优先级大于加减就够了,其他的时候就加括号吧。

Rust 的流程控制

条件语句:if - else if - else 和 Java 的用法类似,但  Rust 的 if-else 还是个表达式,是有返回值的
 1fn main() {
 2    println!("{}", desc(3)) // odd
 3}
 4
 5fn desc(n: i32) -> &'static str {
 6    if n % 2 == 0 {
 7        "even"
 8    } else {
 9        "odd"
10    }
11}
循环语句有三种:
  1. loop {...} 其中加条件执行 break,有类似 Java 的 do {...} while(true) 类似功用
  2. while true {...}
  3. for count in 1..=10 {...}

Rust 循环中的 break, continue 和 Java 类似,不过 break 还能有返回值
1let items = vec![1, 2, 3];
2for item in items {
3}
4for item in items { // 这次会失败,因为 Rust 认为 items 不再需要了,需在第一次 for 时改为 for item in &items {...}
5}

要在遍历时修改元素内容,用
1for item in &mut collection { ... }
不推荐用 index 去访问集合,两原因: 1) index 要求 Rust 去做越界检查,影响性能,  2) 安全, for loop 访问一次可让集合下次不可用

break + label 跳出外层循环
1'outer: for x in 0.. {
2    for y in 0.. {
3        if x + y > 100 {
4            break 'outer
5        }
6    }
7}

break 的返回值
1let n = loo {
2    break 123;
3};

n 的值为 123

Rust 有现代语言的模式匹配功能,用 match, Rust 没有 switch..case 语句。Rust 的 match 必须穷举所有可能性,默认分支用 _ 可放在最后。
1match age {
2    0 => println!("baby"),
3    1..=2 => println!("toddler"),
4    3 | 4 | 5 => (),
5    // ...
6    _=> println!("others")
7}

也可以用 match 来匹配 Some(7), None 等 Option<i32> 值.

匹配 Option 时自动推断类型也有意思,比如
1let age = Option::from(6);
2match age {
3    Some(6) => println!("haha"),
4    _ => ()
5}

age 推断为 Option<i32>, 所以 Some(6) 也是 Some(i32), 如果把 Some(6) 改为 Some(6i8), age 也会被重新推断为 Option<i8>。要是不让 age 自动推断,写成

1let age:Option<u8> = Some(6);
2match age {
3    Some(6u16) => println!("haha"),
4    _ => ()
5}

以上代码无法通过编译
1error[E0308]: mismatched types
2 --> src/main.rs:4:14
3  |
43 |     match age {
5  |           --- this expression has type `Option<u8>`
64 |         Some(6u16) => println!("haha"),
7  |              ^^^^ expected `u8`, found `u16`

let 是变量绑定(变量声明), if let 和  while let 模式匹配它 if 和 while 在判断条件的同时还能进行变量的绑定,这与 Scala 的 case Some(x) 类似。
 1fn main() {
 2    if_let(Some(8));
 3    if_let(None);
 4}
 5
 6fn if_let(value: Option<i32>) {
 7    if let Some(x) = value {
 8        println!("{}", x)
 9    }
10}

只要不是 None 就能匹配为 Some(x), 同时捕获 value 中的值绑定到 x

if let 像是下面的 match 语法糖
1fn if_let(value: Option<i32>) {
2    match value {
3        Some(x) => println!("{}", x),
4        _ => ()
5    }
6}

let Some(x) 的另类用法
1let Some(x) = Some(6) else { todo!() };
2println!("{}", x);   // 6

while let 也是一样的
1let mut vec = vec![1, 2, 3];
2while let Some(value) = vec.pop() { // vec.pop() 在没有元素时 pop 出来的是 None 值
3    println!("{}", value);
4}

它相当的 match 写法是
1let mut vec = vec![1, 2, 3];
2loop {
3    match vec.pop() {
4        Some(value) => println!("{}", value),
5        None => break,
6    }
7}

如果把 if let, while let 靠向 if, while 语句去理解,它们后面要求的是一个 bool 值,就会有疑问,为什么

if let Some(x) = value 中的 let Some(x) = value 只有有值时整体才为 true 呢?同样的在

while let Some(value) = vec.pop() 中为什么 let Some(value) = vec.pop() 有值时整体才为 true。

后记:

Rust 变量默认是 Immutable, 那是来真的,没有用 mul 修饰的话,变量的内容也是无法修改的,这与 Java 不同。

println! 宏后端调用了 std::fmt::format 宏,它的具体用法见 Rust 官方的 Module std::fmt, 它像 Python 的 format 那样可以指定输出格式,用序号或命名变量,它与 to_string() 方法,fmt::Display, fmt::Debug trait 相关。

Rust 没有构造函数的概念,许多类型实现了 new() 静态方法(这不是 Rust 语言的一部分),创建一个类型一般用 Complex {re: 2.1, im:-1.2} 或 Complex::new(11.1, 22.2) 两种方式。

Rust 的 if, match 都是表达式,是有返回值的, {} 也是有返回值
1let y = {
2    let x = 2;
3    x + 1
4};

在 Rust 中,后面不加 ; 分号的是表达式(Expression),有返回值,没 ; 分号的是语句(Statement),无返回值(或说返回值为 Unit), return x; 本身是一条句,但 return 表达了返回值。 永久链接 https://yanbin.blog/rust-language-learning-2/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。