「 Rust 」概念初识

「 Rust 」概念初识


单次猜测

获取用户的命令行输入

1
2
3
4
5
6
7
8
use std::io;

fn main() {
println!("guess a number");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("error reading line");
println!("your number is {}", guess);
}

库的引用

默认情况下 Rust 会将 prelude 模块(预导入模块)的内容导入到每个程序的作用域中,如果要使用的库不位于 prelude 模块中,则需要通过 use 关键字显示的导入。在此示例中,获取用户命令行输入的库为 io 库,而 io 库位于 Rust 标准库 std 中,导入方法为 use std::io;

变量的不可变

默认情况下,Rust 的变量均为不可变的(immutable)。

1
2
3
4
5
fn main() {
let foo = 1;
foo = 2;
println!("foo is {}", foo)
}
1
2
3
4
5
6
7
8
9
10
11
$ cargo run
error[E0384]: cannot assign twice to immutable variable `foo`
--> src/main.rs:3:5
|
2 | let foo = 1;
| ---
| |
| first assignment to `foo`
| help: consider making this binding mutable: `mut foo`
3 | foo = 2;
| ^^^^^^^ cannot assign twice to immutable variable

如果要声明一个可变的变量,那么需要在变量前加上 mut 关键字

1
2
3
4
5
fn main() {
let mut foo = 1;
foo = 2;
println!("bar is {}", foo)
}
1
2
$ cargo run
bar is 2

需要注意的是,引用默认也是不可变的,而 read_line() 方法会根据用户的输入修改传入的变量,因此,也要对入参声明可变。

关联函数

String::new() 会返回字符串的一个新的实例,内部是 utf-8 编码的,中间的两个冒号表示 new()String 这个类型的关联函数,关联函数表示针对这个类型本身来实现的,不是针对这个类型的某个特定示例来实现的,也就是 new() 不会作用于 guess 实例,类似于 Golang 中的结构体方法。

同理,io::stdin() 会返回一个 Stdin 类型的句柄。

Result

Rust 中有很多种 Result 类型,即有通用泛型的 Result,也有针对特定类型的 Result,例如 io::Result ,Result 实际上就是一个枚举类型,包括两个值,一个是 Ok 一个是 Errexpect() 方法用作错误判断,如果返回的值为 Err,那么会中断程序并将入参输出。

占位符

区别于 Golang,println!() 中如果想输出变量,那么必须要有占位符,即 {}

神秘数字

引入第三方 rand 包,实现随机数的生成

1
2
3
4
5
6
7
8
9
10
11
12
use std::io;
use rand::Rng;

fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("secret_number is {}", secret_number);

println!("guess a number")
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("error reading line");
println!("your number is {}", guess);
}

第三方依赖包

Cargo.tomldependencies 新增 package = version 信息为项目新增第三方依赖包。

^ 表示任何一个与指定版本 api 兼容的库均可以,并且该标识为默认。

Cargo.toml

1
2
3
4
5
6
7
8
9
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "^0.3.14"

首次构建时,cargo 会更新源的 index,根据 Cargo.toml 的内容下载依赖,将下载的依赖信息写入 Cargo.lock 中,并完成源码和依赖的构建,当后续源码或者发生变化,则仅会重新构建变化部分。

1
2
3
4
5
6
7
8
9
10
$ cargo build
Updating crates.io index
Downloaded rand v0.3.23
Downloaded rand v0.4.6
Downloaded 2 crates (87.7 KB) in 0.77s
Compiling libc v0.2.112
Compiling rand v0.4.6
Compiling rand v0.3.23
Compiling hello-world v0.1.0 (/Users/shenxianghong/Documents/Project/Rustaceans/hello-world)
Finished dev [unoptimized + debuginfo] target(s) in 5.06s

Cargo.lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<skip>
[[package]]
name = "libc"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"

[[package]]
name = "rand"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
dependencies = [
"libc",
"rand 0.4.6",
]

当对依赖跨大版本版本更新时,需要手动修改 Cargo.toml ,除了可以通过重新构建的方式,还可以通过 cargo update 重新维护依赖关系。

rand 包升级至 0.7

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
38
$ cargo update
Updating crates.io index
Removing cfg-if v1.0.0
Adding fuchsia-cprng v0.1.1
Removing getrandom v0.1.16
Removing ppv-lite86 v0.2.16
Removing rand v0.7.3
Adding rand v0.3.23
Adding rand v0.4.6
Removing rand_chacha v0.2.2
Removing rand_core v0.5.1
Adding rand_core v0.3.1
Adding rand_core v0.4.2
Removing rand_hc v0.2.0
Adding rdrand v0.4.0
Removing wasi v0.9.0+wasi-snapshot-preview1
Adding winapi v0.3.9
Adding winapi-i686-pc-windows-gnu v0.4.0
Adding winapi-x86_64-pc-windows-gnu v0.4.0
shenxianghong@Corgi hello-world % cargo update
Updating crates.io index
Adding cfg-if v1.0.0
Removing fuchsia-cprng v0.1.1
Adding getrandom v0.1.16
Adding ppv-lite86 v0.2.16
Removing rand v0.3.23
Removing rand v0.4.6
Adding rand v0.7.3
Adding rand_chacha v0.2.2
Removing rand_core v0.3.1
Removing rand_core v0.4.2
Adding rand_core v0.5.1
Adding rand_hc v0.2.0
Removing rdrand v0.4.0
Adding wasi v0.9.0+wasi-snapshot-preview1
Removing winapi v0.3.9
Removing winapi-i686-pc-windows-gnu v0.4.0
Removing winapi-x86_64-pc-windows-gnu v0.4.0

除此之外,cargo update 还可以用于依赖包的小版本升级:当执行升级时,Cargo 会忽略 Cargo.lock,根据 Cargo.toml 的包版本信息,升级到最新的小版本,而不会突破大版本,升级之后 Cargo.lock 会更新,而 Cargo.toml 保持不变,也就是基于语义化的版本升级。

Trait

Trait 可以理解成 Golang 中的接口,定义了许多方法。rand::Rng 就是一个 Trait,定义了一组随机数生成器所需要的方法。rand::thread_rng() 这个函数返回是一个 ThreadRng 类型,本质上是一个运行在本地线程空间中,通过操作系统获取随机数种子的随机数生成器,而 gen_range() 就是 Trait 的方法之一。

不导入 trait,但是使用 trait 方法,会引起报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cargo run
error[E0599]: no method named `gen_range` found for struct `ThreadRng` in the current scope
--> src/main.rs:6:44
|
6 | let secret_number = rand::thread_rng().gen_range(1, 101);
| ^^^^^^^^^ method not found in `ThreadRng`
|
::: /Users/shenxianghong/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.4.6/src/lib.rs:524:8
|
524 | fn gen_range<T: PartialOrd + SampleRange>(&mut self, low: T, high: T) -> T where Self: Sized {
| --------- the method is available for `ThreadRng` here
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
1 | use rand::Rng;
|

比较猜测数字与神秘数字

猜测数字为 string 类型,神秘数字为 int 类型,转换后进行大小比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("secret_number is {}", secret_number);

println!("guess a number");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("error reading line");
let guess: u32 = guess.trim().parse().expect("error parsing guess number");
println!("your number is {}", guess);

match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("You win"),
}
}

枚举

std::cmp::Ordering 是一个枚举类型,包含三个值,分别是 Ordering::LessOrdering::GreaterOrdering::Equal ,枚举类型的使用也需要使用双冒号格式。

match

cmp 方法返回的是 Ordering 类型,根据不同的分支(arm)判断匹配模式,从而执行不同的逻辑,即 => 之后的逻辑,类似于 Golang 中的 switch case 用法。

Shadow

Rust 中允许使用同名的变量来覆盖之前的变量,区别于 Golang,不仅可以用于覆盖值,可以类型也可以不一样。一般用于在不额外声明变量的场景下,进行类型转换。

类型

Rust 是强类型语言,并且具备类型推断的能力,gen_range(1, 101) 会返回 1 到 100 之间的随机整数,Rust 中涵盖此范围的类型很多,比如 i32、u32、i64 等等,如果未做进一步的声明,Rust 默认其为 i32。

可以注意到,变量 guess 被转换成了 u32 类型,而接下来还对变量 guess 和 secret_number 进行了 match 比较,因此 Rust 也会将变量 secret_number 设置为 u32 类型编译,因此,如果没有 match 比较,则 Rust 会将其默认为 i32。

多次猜测

增加死循环,直至猜对退出;增加错误处理,完善健壮性

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
use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);

loop {
println!("guess a number");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("error reading line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue
};
println!("your number is {}", guess);

match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win");
break
},
}
}
}

死循环

Rust 中的死循环使用 loop 关键字,退出使用 break 关键字,继续使用 continue 关键字。

错误处理

Rust 中常用的错误处理方式是基于 match 模式,例如 parse() 方法返回 Result 类型,该类型包括两个枚举值。其中 Ok(num) 表示猜测数字解析成功,num 为解析之后的数字,通过 => 赋值给 guess。同理,Err(_) 表示解析失败,_ 为错误信息,下划线表示忽略。

Author

Shen Xianghong

Posted on

2022-01-08

Updated on

2023-06-19

Licensed under