Rust 语言学习笔记(二)
再继续快速学习一下 Rust 的以下几个知识点,就可以开始着手做点小工具了
&[i32], &str 是 slice1, slice2, slice3 的推断类型,Slice 是一个视图,编译期无法确定长度。
Rust 不同宽度的类型之间不会自动转换的
不同宽度类型之间比较大小也不允许。
struct 和 enum 都支持用
有有参数的枚举
输出
Rust 的标准库 std::collections 提供了 4 种支持泛型的容器类型, Vec<T>, VecDeque<T>, LinkedList<T>, HashMap<K, V>, BTreeMap<K, V>, HashSet<T>, BTreeSet<T>, Binaryheap<T>。
Vec 的初始容量是 0, 不指定 mut 的话,对 v 的内容都不可修改。调用 v.push() 往其中添加元素过程中,v.len 与 v.buf.cap 之间的关系
由此可见,至少从目前的 Vec 实现 (Rust 1.75), Vec 只在容量不足时增长,并且每次翻倍。这种实现效率会有两个问题
1) 扩容太频繁, 消耗资源, Vec 新创建一个 2 倍容量的底层数组,并将现有元素移新缓冲中去
2) 元素越多, v.len 与 v.buf.cap 差值越大,也就造成 Buffer 中浪费的空间就越大. 比如元素数量为 4096 时,再添加一个元素就扩容到了 8092,这时实际使用 4097, 空闲 4095
所以使用 Vec 最好预先估算好容量,或用 LinkedList,如用下面的方式声明 Vec
可用 vec! 宏来声明 Vec
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) 操作
String 本质上是一个字段为 Vec<u8> 的结构体,声明为
它是可变的,字符串内容在堆中,由字符串长度读取到实际内容。创建 String 的方式有
String 的 vec buf 容量也是成倍的增长, 只是 buf 为零时第一次增长不同, 例如下面的代码
如果 x <=8, push_str 之后, s.vec.buf 缓冲大小为 8, x > 8 的话,v.vec.buf 缓冲大小就是 x。String 还可用 "+" 连接 &str 字符串,它会返回新的 String
format! 宏可连接 str 和 Strign
Rust 的 String 不能直接使用索引(s[idx]方式) 来访问其中的字符, 字符串迭代分 bytes() 和 chars() 两种方式。bytes() 迭代出来的是字节的整数值. len() 获取的是以字节为单位的长度。更多 String 的操作就查 API 吧。
数字字面的类型用后缀来表示,所 2u8, 1.2f32. Rust 不支持 ++, 和 -- 操作。Rust 的各种算术运算,关系运算,逻辑运算,位运算和 C 和 Java 是一样的,运算优先级也没必要去记,知道乘除优先级大于加减就够了,其他的时候就加括号吧。
循环语句有三种:
Rust 循环中的 break, continue 和 Java 类似,不过 break 还能有返回值
要在遍历时修改元素内容,用
不推荐用 index 去访问集合,两原因: 1) index 要求 Rust 去做越界检查,影响性能, 2) 安全, for loop 访问一次可让集合下次不可用
break + label 跳出外层循环
break 的返回值
n 的值为 123
Rust 有现代语言的模式匹配功能,用 match, Rust 没有 switch..case 语句。Rust 的 match 必须穷举所有可能性,默认分支用
也可以用 match 来匹配 Some(7), None 等 Option<i32> 值.
匹配 Option 时自动推断类型也有意思,比如
age 推断为 Option<i32>, 所以 Some(6) 也是 Some(i32), 如果把 Some(6) 改为 Some(6i8), age 也会被重新推断为 Option<i8>。要是不让 age 自动推断,写成
以上代码无法通过编译
let 是变量绑定(变量声明), if let 和 while let 模式匹配它 if 和 while 在判断条件的同时还能进行变量的绑定,这与 Scala 的 case Some(x) 类似。
只要不是 None 就能匹配为 Some(x), 同时捕获 value 中的值绑定到 x
if let 像是下面的 match 语法糖
let Some(x) 的另类用法
while let 也是一样的
它相当的 match 写法是
如果把 if let, while let 靠向 if, while 语句去理解,它们后面要求的是一个 bool 值,就会有疑问,为什么
if let Some(x) = value 中的
while let Some(value) = vec.pop() 中为什么
Rust 没有构造函数的概念,许多类型实现了 new() 静态方法(这不是 Rust 语言的一部分),创建一个类型一般用 Complex {re: 2.1, im:-1.2} 或 Complex::new(11.1, 22.2) 两种方式。
Rust 的 if, match 都是表达式,是有返回值的,
在 Rust 中,后面不加
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
- 基本数据类型
- 复合数据类型
- 基本的流程控制
元组
元组和 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
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)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 之间的关系
- 0 -> 0
- 1 ~4 -> 4
- 5 ~ 8 -> 8
- 9 ~ 16 -> 16
- 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<i32> = 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) 操作
- map.insert(key, value): 不存在 key, 插入新的键值对,已存在 key 则更新对应值, 总是覆盖旧值
- entry(key).or_insert(value):不存在 key, 插入新的键值对,已存在 key 则不作任何操作, 不覆盖旧值
字符串(str 和 String)
&str 的创建1let s1 = "Hello, World";
2let str = String::from("Hello");
3let s2 = str.as_str();1pub struct String {
2 vec: Vec<u8>,
3}1let mut s1 = String::new();
2let s2 = String::from("Hello World!");
3let str = "Hello, Rult!";
4let s3 = str.to_string();1let mut s = String::new();
2let x = 8;
3s.push_str("a".repeat(x).as_str());1let s1 = String::from("Hello");
2let s2 = String::from(" Rust");
3let s3 = " World";
4let mut s = s1 + &s2 + s2.as_str() + s3;1let s = format!("{}-{}-{}", s1, s3, "Yet")数字字面的类型用后缀来表示,所 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}- loop {...} 其中加条件执行 break,有类似 Java 的 do {...} while(true) 类似功用
- while true {...}
- 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 { ... }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) 进行许可。