Rust 杂记
本文记录一些自己在学习 Rust 过程中的理解
枚举的基本使用
枚举的定义比较简单:
1
2
3
4
5
6
enum Direction {
Up,
Down,
Left,
Right,
}
使用 match
对枚举进行匹配的时候,需要匹配所有的可能性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Direction {
Up,
Down,
Left,
Right,
}
fn which_way(go: Direction) -> String {
match go {
Direction::Down => "Down".to_string(),
Direction::Left => "Left".to_string(),
Direction::Right => "Right".to_string(),
Direction::Up => "Up".to_string(),
}
}
如果有些可能性无关紧要,可以用一个变量代替:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[derive(Debug)]
enum Direction {
Up,
Down,
Left,
Right,
}
fn which_way(go: Direction) -> String {
match go {
Direction::Down => "Down".to_string(),
Direction::Left => "Left".to_string(),
any_var => format!("{:?} is irrelevent", any_var),
}
}
这里为了使用
any_var
这个变量,并返回一个字符串,需要给Direction
这个枚举加一个Debug
特征。总之,这里的意思是,如果go
是Direction::Right
或者Direction::Up
那么都会转到any_var
这个分支上去,并且赋值给any_var
这个变量。
如果这个变量也用不到,则可以用 _
代替:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Direction {
Up,
Down,
Left,
Right,
}
fn which_way(go: Direction) -> String {
match go {
Direction::Down => "Down".to_string(),
Direction::Left => "Left".to_string(),
_ => "I don't care".to_string(),
}
}
但是,默认来说,枚举值是不能进行比较的。如果要进行比较,需要加 PartialEq
特征:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[derive(PartialEq)]
enum Direction {
Up,
Down,
Left,
Right,
}
fn is_left(go: Direction) -> bool {
if go == Direction::Left {
true
} else {
false
}
}
不过这个样子好像也挺不专业的,其实完全可以用 match
代替了:
1
2
3
4
5
6
7
8
9
10
11
12
13
enum Direction {
Up,
Down,
Left,
Right,
}
fn is_left(go: Direction) -> bool {
match go {
Direction::Left => true,
_ => false,
}
}
另外枚举没有 Copy
特征,所以二次赋值或者传参会转交所有权。除非加上 Copy
特征:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#![allow(dead_code)]
#[derive(Clone, Copy)]
enum Direction {
Up,
Down,
Left,
Right,
}
fn which_way(go: Direction) -> String {
match go {
Direction::Down => "Down".to_string(),
Direction::Left => "Left".to_string(),
Direction::Right => "Right".to_string(),
Direction::Up => "Up".to_string(),
}
}
fn is_left(go: Direction) -> bool {
match go {
Direction::Left => true,
_ => false,
}
}
fn main() {
let go = Direction::Right;
println!("{}", which_way(go));
println!("{}", is_left(go));
}
枚举的高级用法
使用 官方文档 的例子,我们可以这样定义一个 IP 地址:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#![allow(dead_code, unused_variables)]
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let home = IpAddr {
kind: IpAddrKind::V4,
address: "127.0.0.1".to_string(),
};
}
但是多用一个结构体来绑定 IP 类型和地址,有点冗余,枚举本身也支持给变体(Variant)绑定数据:
1
2
3
4
5
6
7
8
9
10
#![allow(dead_code, unused_variables)]
enum IpAddrKind {
V4(String),
V6(String),
}
fn main() {
let home = IpAddrKind::V4("127.0.0.1".to_string());
}
此时 IpAddrKind::V4
就变成了一个函数,接收一个 String
参数。
不同的变体可以接收不同的参数:
1
2
3
4
5
6
7
8
9
10
11
12
#![allow(dead_code, unused_variables)]
enum IpAddrKind {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddrKind::V4(127, 0, 0, 1);
let loopback = IpAddrKind::V6(String::from("::1"));
}
有数据的变体和没有数据的变体可以共存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#![allow(dead_code, unused_variables)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let quit_msg = Message::Quit;
let move_msg = Message::Move { x: 10, y: 20 };
let write_msg = Message::Write("Hello".to_string());
let change_msg = Message::ChangeColor(144, 234, 4);
}
高级匹配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#![allow(dead_code)]
enum Discount {
Percent(f64),
Flat(i32),
}
fn main() {
let flat4 = Discount::Flat(4);
match flat4 {
Discount::Flat(2) => println!("it's flat 2"),
Discount::Flat(4) => println!("it's flat 4"),
_ => println!("Don't care"),
}
}
匹配第二个分支,而不会匹配第一个分支。也可以使用如下方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#![allow(dead_code)]
enum Discount {
Percent(f64),
Flat(i32),
}
fn main() {
let flat4 = Discount::Flat(4);
match flat4 {
Discount::Flat(num) => println!("it's flat {}", num),
_ => println!("Don't care"),
}
}
结构体的定义和使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#![allow(dead_code)]
struct ShippingBox {
depth: i32,
width: i32,
height: i32,
}
fn main() {
let my_box = ShippingBox {
depth: 3,
width: 2,
height: 5,
};
let tall = my_box.height;
print!("the box is {} units tall", tall);
}
注意,如果 my_box.height
的类型没有 Copy
特征,那么 let tall = my_box.height
这种赋值操作也会转交所有权。
如果变量与结构体的域(field)相同,可以使用简单写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct Position {
x: i32,
y: i32,
}
fn main() {
let x = 10;
let y = 20;
let p = Position { x, y };
//而不必
//let p = Position { x: x, y: y };
println!("({}, {})", p.x, p.y);
}
结构体也可以用 match
匹配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Worker {
age: i32,
salary: i32,
}
fn main() {
let w = Worker {
age: 32,
salary: 10,
};
match w {
Worker { salary: 10, age } => println!("age: {age}, salary: 10"),
Worker { age, .. } => println!("The age is {}", age),
}
}
第一个分支中给定了 salary
的值,因此该分支下并没有 salary
这个变量,只有 age
变量。
第二个分支只关心 age
这个数据,其他的不关心,可以用 ..
代替。
元组的定义和解构
1
2
3
4
5
6
7
fn main() {
let person_info: (String, i32) = (String::from("John"), 34);
let (name, age): (String, i32) = person_info;
println!("{} is {} years old.", name, age);
// println!("{}", person_info.0);
println!("age: {}", person_info.1);
}
元组的类型声明就是 用圆括号括起固定数量的类型。
元组的值就是 用圆括号括起固定数量的值。
解构的过程就是在等号左边用圆括号括起固定数量的变量名,以匹配等号右侧的元组值。
解构也相当于赋值,因此如果某些值没有 Copy
特征,则解构后,那一部分值就无法再使用。比如上例中的 person_info.0
便无法获取,但这不影响 person_info.1
的正常获取。
impl 的基本使用
一段 C 风格的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
struct Temperature {
degree: f64,
}
fn show_temp(temp: Temperature) {
println!("The temperature is {}", temp.degree);
}
fn main() {
let hot = Temperature { degree: 99.9 };
show_temp(hot)
}
此时结构体 Temperature
和函数 show_temp
其实没什么关系,只不过后者的参数是前者。
impl
关键词可以把两者结合起来,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Temperature {
degree: f64,
}
impl Temperature {
fn show_temp(temp: Temperature) {
println!("The temperature is {}", temp.degree);
}
}
fn main() {
let hot = Temperature { degree: 99.9 };
Temperature::show_temp(hot)
}
注意,此时 show_temp
属于 Temperature
域了,要调用需要加前缀,但是除此之外也没啥大的区别。
如果要有点区别,可以把 show_temp
的第一个参数改为 self
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Temperature {
degree: f64,
}
impl Temperature {
fn show_temp(self: Temperature) {
println!("The temperature is {}", self.degree);
}
}
fn main() {
let hot = Temperature { degree: 99.9 };
// Temperature::show_temp(hot)
hot.show_temp();
}
当改成 self
之后,就可以使用类似 host.show_temp()
的方式调用函数了。当然,Temperature::show_temp(hot)
的方式也管用。
在 impl
中的函数有个方便之处是,可以用 Self
类型代替结构体类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Temperature {
degree: f64,
}
impl Temperature {
fn show_temp(self: Self) {
println!("The temperature is {}", self.degree);
}
}
fn main() {
let hot = Temperature { degree: 99.9 };
hot.show_temp();
}
虽然 Self
是个特殊的类型,但是用起来也跟其他的类型没区别,在这里它只是 Temperature
的替身而已。所以,因为 Temperature
没有 Copy
特征,所以这个传参会转交所有权,hot.show_temp()
之后我们就无法使用 hot
这个变量了。通常这不是我们希望的情况,解决办法也跟普通类型没区别,借用就好了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Temperature {
degree: f64,
}
impl Temperature {
fn show_temp(self: &Self) {
println!("The temperature is {}", self.degree);
}
}
fn main() {
let hot = Temperature { degree: 99.9 };
hot.show_temp();
println!("temp: {}", hot.degree);
}
此时语法糖又来了,我们可以用 &self
代替 self: &Self
,所以这就是我们常见的 impl
中函数的样子了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Temperature {
degree: f64,
}
impl Temperature {
fn show_temp(&self) {
println!("The temperature is {}", self.degree);
}
}
fn main() {
let hot = Temperature { degree: 99.9 };
hot.show_temp();
println!("temp: {}", hot.degree);
}
虽然这个样子不是很直观,但是我们也要知道,这里的 &self
是 Temperature
示例(也就是 hot
)的一个不可变借用。
如果要使用可变借用,可以使用 &mut self
,也就是 self: &mut Self
的语法糖。
当然,要允许拥有可变调用,实例本身也必须是可变的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Temperature {
degree: f64,
}
impl Temperature {
fn show_temp(&mut self) {
println!("The temperature is {}", self.degree);
}
}
fn main() {
let mut hot = Temperature { degree: 99.9 };
hot.show_temp();
println!("temp: {}", hot.degree);
}
列表的定义与使用
1
2
3
4
5
6
7
fn main() {
let a: [i32; 3] = [1, 2, 4];
println!("{:?}", a);
println!("second: {}", a[1]);
let [c, d, e]: [i32; 3] = a;
println!("[{c}, {d}, {e}]");
}
列表没有实现 std::fmt::Display
特征,所以无法使用 "{}"
打印,但是可以用 "{:?}"
打印。
列表获取索引的方式与元组不同,列表用方括号。
解构则类似。
列表是统一类型,固定长度的,不能 push
或者 pop
。
快速生成列表:
1
2
3
4
fn main() {
let a = [1; 3];
println!("{:?}", a);
}
Vector 的创建和使用
创建方式一:
1
2
3
4
5
6
7
fn main() {
let mut v1: Vec<i32> = Vec::new();
v1.push(1);
v1.push(2);
v1.push(3);
println!("{:?}", v1);
}
创建方式二:
1
2
3
4
fn main() {
let v2 = Vec::from([1, 2, 3]);
println!("{:?}", v2);
}
创建方式三:
1
2
3
4
fn main() {
let v3 = vec![1, 2, 3];
println!("{:?}", v3);
}
数组的容量和长度是不一样的:
1
2
3
4
5
6
fn main() {
let mut v4: Vec<i32> = Vec::with_capacity(5);
println!("{:?}, {}", v4, v4.len());
v4.resize(v4.capacity(), 1);
println!("{:?}, {}", v4, v4.len());
}
遍历数组:
1
2
3
4
5
6
fn main() {
let v = vec![2, 4, 6, 8];
for n in v {
println!("{}", n);
}
}
这里的遍历隐式调用了 v.into_iter()
,而该函数的签名是 fn into_iter(self) -> Self::IntoIter
,传参并不是引用,所以会直接转移数组的所有权(因为 Vec
没有 Copy
特征)。所以在遍历之后,就不能再使用 v
变量了。
解决办法:
1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let v = vec![2, 4, 6, 8];
let mut c: usize = 0;
for n in &v {
println!("as value: {}", n);
println!("as addr : {:p}", n);
println!("its addr: {:p}", &n);
println!("idx addr: {:p}", &v[c]);
println!("-----------");
c += 1;
}
}
遍历时用 &v
就好了,这样只会借用,而不是转交所有权了。
此时 n
的类型是 &i32
,也就是数组中每个元素的引用。
遍历下来,每次循环中,第一行打印发生了隐式的从 &i32
到 i32
的转换,实际上 n
的值是一个地址,因为其类型是引用。这个值与 &v[c]
相同,即数组中每个元素的地址。
在所有循环中,&n
的值都不变,也就是 n
的地址没变。
这里的
c
必须是usize
类型,这是v[c]
的要求。
对 & 的理解
在讲 &
之前,先讲讲数学中的根号:
我上学的时候听到过这么一句话,平方根的结果有正有负,算术平方根的结果只有正。
小时候就把这句话这么记住了,但是却不明白,为什么算术平方根的结果只有正?为什么会有平方根和算术平方根的区别?
不管区别是什么吧,后来我明白了一件事,平方根这个符号,有两个含义。
第一种含义是 动词性质 的含义,意思是要取某个值的平方根,它代表的是一种运算过程,也就是说平方根这个符号在这里是一个运算符,比如:
\[\sqrt{4} = \pm2\]在这里,根号 4 是一个运算过程,其结果是 2 或者 -2。
第二种含义是 形容词性质 的含义,意思是某个值的平方根,它仅仅代表一个值,是一种数值的写法,比如:
\[\sqrt{7}\]这里根号 7 并不是要运算 7 的平方根,而仅仅是根号 7 这个数值的写法,就如同 -2 就是 -2,是这个数字的写法,并不表示减去 2。
因此这里根号 7 就是正的,而不是既有正又有负。
回到 &
,这个符号也有两种含义:
一种是动词性质的,表示取某个变量的地址,比如:
1
2
let a = 10;
let b = &a;
第二种是形容词性质的,表示某个类型是个引用类型,比如:
1
fn say_hi(name: &String) {}
&str 的基本使用
to_string
可以把一些类型转换为 String
类型:
1
2
3
4
5
6
7
fn main() {
let s: &str = "hello";
let n: i32 = 10;
let ss: String = s.to_string();
let nn: String = n.to_string();
println!("{}, {}", ss, nn);
}
to_owned
可以把引用类型通过 Clone 转为有所有权的类型:
1
2
3
4
5
6
7
fn main() {
let s: &str = "hello";
let n: &i32 = &10;
let ss: String = s.to_owned();
let nn: i32 = n.to_owned();
println!("{}, {}", ss, nn);
}
对于 &str
来说,两者作用差不多。
向函数传递字符串参数时,推荐使用 &str
,因为更快:
1
2
3
4
5
6
7
fn say_hello(name: &str) {
println!("Hello, {}", name);
}
fn main() {
say_hello("John");
}
在结构体中使用 &str
是一件比较麻烦的事情,因为需要保证结构体中 &str
类型的变量至少要活得跟结构体实例一样久,因此需要指定生命周期:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Person<'a> {
name: &'a str,
}
fn a_person(name: &str) {
let p = Person { name };
println!("hello, {}", p.name);
}
fn main() {
let name = "John";
a_person(name);
println!("{} is still here", name);
}
所以,结构体中保存字符串,建议使用 String
。
Option 的基本使用
Option
是一个枚举,因此需要先了解 Rust enum 。
使用场景一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Person {
age: i32,
name: String,
}
fn find_age(name: &str, persons: &Vec<Person>) -> Option<i32> {
for person in persons {
if person.name == name {
return Some(person.age);
}
}
None
}
fn main() {
let persons_info = vec![
Person { name: "John".to_string(), age: 32 },
Person { name: "Karl".to_string(), age: 45 },
Person { name: "Liz".to_string(), age: 24 },
];
let age = find_age("Karl", &persons_info);
match age {
None => println!("age not found"),
Some(a) => println!("the age is {a}"),
}
}
使用场景二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let mut nums = vec![1, 2, 3];
loop {
let n = nums.pop();
match n {
None => {
println!("no more numbers");
break;
}
Some(i) => {
println!("{i} is popped");
}
}
}
}
Result 的基本使用
Result
是一个枚举,因此需要先了解 Rust enum 。
Result
的定义:
1
2
3
4
enum Result<T, E> {
Ok(T),
Err(E)
}
错误类型 E
也是一个泛型,因此可以是任何类型,数字、字符串,或者更复杂的结构体。
使用场景
使用场景一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct SoundData {
kind: String,
}
impl SoundData {
fn new(kind: &str) -> Self {
Self { kind: kind.to_string() }
}
}
fn get_sound(kind: &str) -> Result<SoundData, String> {
if kind == "alert" {
Ok(SoundData::new("alert"))
} else {
Err("Unable to find sound data".to_string())
}
}
fn main() {
let sound = get_sound("alert");
match sound {
Ok(sd) => println!("sound data located: {}", sd.kind),
Err(e) => println!("error: {e}"),
}
}
使用场景二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Debug)]
enum MenuChoice {
MainMenu,
Start,
Quit,
}
fn get_choice(input: &str) -> Result<MenuChoice, String> {
match input {
"mainmenu" => Ok(MenuChoice::MainMenu),
"start" => Ok(MenuChoice::Start),
"quit" => Ok(MenuChoice::Quit),
c => Err(format!("menu choice [{c}] not found")),
}
}
fn main() {
let choice = get_choice("start");
match choice {
Ok(mc) => println!("found a choice: {mc:?}"),
Err(e) => println!("error: {e}"),
}
}
使用场景三:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct Adult {
name: String,
age: u8,
}
impl Adult {
fn new(name: &str, age: u8) -> Result<Self, String> {
if age >= 21 {
Ok(Self { name: name.to_string(), age })
} else {
Err("the age should be older than 21".to_string())
}
}
}
fn show(a: &Result<Adult, String>) {
match a {
Ok(adult) => println!("{} is {} years old", adult.name, adult.age),
Err(err) => println!("error: {err}"),
}
}
fn main() {
let a1 = Adult::new("John", 14);
let a2 = Adult::new("Karl", 34);
show(&a1);
show(&a2);
}
? 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#[derive(Debug)]
enum MenuChoice {
MainMenu,
Start,
Quit,
}
fn get_choice(input: &str) -> Result<MenuChoice, String> {
match input {
"mainmenu" => Ok(MenuChoice::MainMenu),
"start" => Ok(MenuChoice::Start),
"quit" => Ok(MenuChoice::Quit),
c => Err(format!("menu choice [{c}] not found")),
}
}
fn print_choice(input: &str) -> Result<(), String> {
let choice: MenuChoice = get_choice(input)?;
println!("the choice: {choice:?}");
Ok(())
// let choice: Result<MenuChoice, String> = get_choice(input);
// match choice {
// Ok(c) => {
// println!("the choice: {c:?}");
// Ok(())
// }
// Err(e) => Err(e),
// }
}
fn main() {
let r = print_choice("quit");
match r {
Ok(_) => {}
Err(e) => println!("error: {e}"),
}
}
print_choice
函数中,没有注释的内容与注释的内容作用是相同的,但是更简洁。
get_choice(input)?
会在遇到 Err
的时候 直接返回 Err
,如果不是,则将 Ok
中的数据返回给 choice
。
因此,两个 choice
的类型是不一样的。
对所有权转移的理解
看如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Position {
x: i32,
y: i32,
}
impl Position {
fn origin() -> Self {
Self { x: 0, y: 0 }
}
fn show(&self) {
println!("({}, {})", self.x, self.y);
}
}
fn main() {
let o = Position::origin();
o.show();
}
在 origin
中,返回值毫无疑问发生了所有权转移,但是这个转移具体发生了什么,需要理解一下。
实际上,move 和 copy 发生的操作差不多,都是把数据从一个地方转移到了另一个地方,不过区别是,move 之后的原数据就不能用了,copy 之后的原数据还可以继续用。
我们打印一下地址看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#![allow(dead_code)]
struct Position {
x: i32,
y: i32,
}
impl Position {
fn origin() -> Self {
let ori = Self { x: 0, y: 0 };
println!("in stack: {:p}", &ori);
ori
}
}
fn main() {
let ori = Position::origin();
println!("out stack: {:p}", &ori);
}
输出类似于:
1
2
in stack: 0xda1f32fa38
out stack: 0xda1f32fad8
地址并不一样。
所以转移所有权也会发生地址变动,可以将其理解为拷贝吧,只不过拷贝之后原地址就不可用了。
除非重新赋值:
1
2
3
4
5
6
7
8
fn main() {
let mut s = String::from("hello");
println!("{:p}", &s);
let t = s;
println!("{:p}", &t);
s = String::from("world");
println!("{:p}", &s);
}
输出类似于:
1
2
3
0x41d26ff570
0x41d26ff5d0
0x41d26ff570
当然,要允许重新赋值,就得保证 s
是可变的,如果不可变的话,连重新赋值也不能了。
Rust Book 的第一个例子
1. 获取一行输入
1
2
3
4
5
6
7
8
use std::io;
fn main() {
println!("猜测一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
println!("您猜测的数是:{}", guess);
}
首先创建一个字符串变量用来保存读取到的内容,这个变量需要是 可变 的,使用 String::new()
创建。
然后使用 io:stdin().read_line
来读取,参数是一个 可变引用 类型。
读取可能会出错,因此在后面接一个 expect
函数,参数是错误提示信息。
2. expect 的表现
1
2
3
4
fn main() {
let e: Result<i32, &str> = Err("错误信息");
e.expect("出现错误");
}
报错信息的格式为:出现错误: "错误信息"
。
这个 Result
全称是 std::result::Result
,是较通用的类型,还有其他特定的类型比如 std::io::Result
等。
3. 生成随机数
首先在项目下运行 cargo add rand
,添加这个库。
1
2
3
4
5
6
use rand::Rng;
fn main() {
let num = rand::thread_rng().gen_range(1..101);
println!("The number: {}", num);
}
rand::thread_rng
是一个随机数生成器, 包含在 rand::Rng
这个 Trait 里面。
其中 gen_range
用于在指定的范围里生成一个随机数。
1..101
表示 1 <= x < 101
,1..=100
表示 1 <= x <= 100
。
4. 用 match 比较大小
1
2
3
4
5
6
7
8
9
10
11
use std::cmp::Ordering;
fn main() {
let a = 10;
let b = 20;
match a.cmp(&b) {
Ordering::Less => println!("小了"),
Ordering::Greater => println!("大了"),
Ordering::Equal => println!("正好"),
}
}
5. 用 match 替换 expect
使用 expect
会在出错时直接使程序崩溃,比如:
1
2
3
4
5
6
7
fn main() {
println!("输入一个数字");
let mut num = String::new();
std::io::stdin().read_line(&mut num).expect("无法读取行");
let num: i32 = num.trim().parse().expect("无法转换为数字");
println!("转换后:{}", num);
}
如果输入的字符串不是纯数字,程序会在第 5 行崩溃,崩溃后的代码都不会运行。但有时候我们不想这样,出现错误就使程序崩溃不是一种正确的错误处理办法。
此时我们可以改成这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
loop {
println!("输入一个数字");
let mut num = String::new();
std::io::stdin().read_line(&mut num).expect("无法读取行");
let num: i32 = match num.trim().parse() {
Ok(n) => n,
Err(_) => {
println!("输入的不是一个纯数字!");
continue
}
};
println!("转换后:{}", num);
break
}
}
这是一种常见的错误处理方式。
同样,对于读取字符串过程中的错误处理,也可以改成如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn main() {
loop {
println!("输入一个数字");
let mut num = String::new();
match std::io::stdin().read_line(&mut num) {
Ok(_) => {},
Err(err) => {
println!("无法读取行:{}", err);
break
},
};
let num: i32 = match num.trim().parse() {
Ok(n) => n,
Err(_) => {
println!("输入的不是一个纯数字!");
continue
}
};
println!("转换后:{}", num);
break
}
}
其中 read_line
成功后会返回读取到的字节数,是一个 usize
类型,不过这里我们不关心,因此直接用 Ok(_) => {}
,如果出错的话,就打印错误信息,然后中止循环。
6. for 循环遍历固定次数
1
2
3
4
5
6
7
8
9
10
fn main() {
println!("正向:");
for i in 1..5 {
println!("{i}");
}
println!("逆向:");
for j in (1..5).rev() {
println!("{j}");
}
}
处理 JSON 数据
添加 JSON 库:
1
cargo add serde_json
官方 API 文档:https://docs.rs/serde_json/latest/serde_json/
基础
在 Rust 中处理 JSON 数据的三种方式:
- 作为文本数据。意思就是你拿到的 JSON 数据是一段纯文本,比如直接从文本读取的等。
- 作为一种无类型形式的数据。把 JSON 文本转换为没有严格的类型限定的 Rust 数据格式进行处理。
- 作为强类型数据结构。把 JSON 文本转换为严格类型限定的 Rust 数据格式。
处理无类型 JSON 数据
任何合法的 JSON 文本都可以转换成由如下枚举递归而成的数据形式:
1
2
3
4
5
6
7
8
enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
这个数据结构叫 serde_json::Value
。
可以用 serde_json::from_str
函数从文本读取 JSON 数据,然后转换为 serde_json::Value
格式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use serde_json::{Result, Value};
fn untyped_example() -> Result<()> {
let data = r#"
{
"name": "John Doe",
"age": 43,
"phones": [
"+44 12456",
"+44 24352"
]
}
"#;
let v: Value = serde_json::from_str(data)?;
println!("Please call {} at the number {}", v["name"], v["phones"][0]);
Ok(())
}
其中 v["name"]
的类型是 &Value
,因此打印出来是形如 "John Doe"
的样子,带着一个双引号。
要去除这个双引号,可以使用 as_str()
方法,这个方法返回一个 Option<&str>
类型,之所以是 Option
类型,是因为并不是所有的 &Value
都能转成字符串,比如 v["age"]
就不行,可以转就是一个 Some(&str)
,转不了就是一个 None
。
如果说有的键值不存在,比如 v["abc"]
,则会返回 Value::Null
。
处理强类型 JSON 数据
需要安装 serde
并且开启 derive
功能:
1
cargo add serde -F derive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use serde::{Deserialize, Serialize};
use serde_json::Result;
#[derive(Serialize, Deserialize)]
struct Person {
name: String,
age: u8,
phones: Vec<String>,
}
fn typed_example() -> Result<()> {
let data = r#"
{
"name": "John Doe",
"age": 43,
"phones": [
"+44 12456",
"+44 24352"
]
}
"#;
let p: Person = serde_json::from_str(data)?;
println!("Please call {} at the number {}", p.name, p.phones[0]);
Ok(())
}
构建 JSON 数据
serde_json
提供了 json!
宏来构建 serde_json::Value
对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use serde_json::json;
fn main() {
let john = json!({
"name": "John Doe",
"age": 43,
"phones": [
"+44 12345",
"+44 34243"
]
});
println!("First phone number: {}", john["phones"][0]);
println!("{}", john.to_string());
}
to_string
函数可以把 serde_json::Value
对象转成字符串。json!
宏内也可以直接写变量,只要能序列化的就行。比如 "name": full_name
。
但是问题依旧,这种方式并没有类型提示。
序列化数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use serde::{Deserialize, Serialize};
use serde_json::Result;
#[derive(Serialize, Deserialize)]
struct Address {
street: String,
city: String,
}
fn print_an_address() -> Result<()> {
let address = Address {
street: "10 Downing Street".to_owned(),
city: "London".to_owned(),
};
let j = serde_json::to_string(&address)?;
println!("{}", j);
Ok(())
}