一、简介
Rust是一种预编译静态语言,这意味着你可以编译程序并将可执行文件发送给其他人。
二、安装
Windows直接去官网下载相关软件程序包即可。它可使用IDE软件进行开发。
三、编程
Rust并不关心代码存放位置。Rust源文件总是以 .rs
扩展名结尾。Rust相关代码编写完成后需要先编译在运行。
编写:Hello, world!
fn main() {
println!("Hello, world!");
}
以在Windows上为例:
> rustc main.rs
> .\main.exe
Hello, world!
分析:Hello, world!
fn main() {
}
fn
语法声明了一个信的函数,小括号()
表明没有参数,大括号{}
作为函数体的开始和结束。main
函数是一个特殊的函数:在可知性的Rust程序中,它总是最先运行的代码。第一个行代码声明了一个叫做main
的函数,它没有参数也没有返回值。如果有参数,它们的名称应出现在小括号()
中。
在main
函数中有如下代码:
println!("Hello, world!");
- Rust的缩进风格使用4个空格,而不是1个制表符(tab);
-
println!
调用了一个Rust宏(macro);如果是调用函数,则应输入println
(没有`!) -
"Hello, world!"
是一个字符串。 - 该行以分号(
;
)结尾,这代表一个表达式的结束和下一个表达式的开始。大部分Rust代码行以分号结尾。
四、Cargo
Cargo是Rust的构建系统和包管理器。
1、创建Cargo项目
> cargo new hello_cargo
> cd hello_cargo
第一行新建了名为hello_cargo
的目录和项目。而在这个目录下包含:一个Cargo.toml
文件、一个src
目录、以及位于src
目录下的main.rs
文件。
文件名:Cargo.toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
这个文件使用TOML
格式,这是Cargo配置文件的格式。
第一行,[package]
,是一个片段标题,表明下面的语句用来配置一个包。
之后的三行设置了Cargo编译程序所需的配置:项目名称、项目版本以及要使用的Rust版本。
最后一行,[dependencies]
是罗列项目依赖的片段的开始,代码包被称为crates
。
Cargo将源文件放在src
目录下。项目根目录只存放README、license信息、配置文件和其他跟代码无关的文件。
2. Cargo使用方式
-
cargo new
创建项目; -
cargo build
构建项目; -
cargo run
一步构建并运行项目; -
cargo check
在不生成二进制文件的情况下构建项目并检查错误;
3. 发布(release)构建
当项目最终准备好发布时,可以使用cargo build --release
来优化编译项目。这会在target/release
而不是target/debug
下生成可执行文件。
五、猜测数字游戏
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
默认情况下,Rust设定了若干会自动导入到每个程序作用域中的标准库内容,这组内容被成为预导入(preclude)
内容。使用use
预计显示将相关库引入作用域。
use std::io;
io
库来自于标准库,也被称为std
。
fn main() {
main
函数是程序的入口点。
1.使用变量存储值
let mut guess = String:new();
创建一个变量(variable)来存储用户输入。在Rust中,变量默认是不可变的。进而使用mut
使得变量可变。let mut guess
会引入一个叫做guess
的可变变量,等号=
告诉Rust现在想将某值绑定在等号=
右边的guess
变量上。String
是一个标准库提供的字符串类型,它是UTF-8
编码的可增长文本块。::new
那一行的::
语法表明new
是String
类型的一个关联函数。关联韩式是针对类型实现的。一些语言中把它成为静态方法。
2.接收用户输入
io::stdin()
.read_line(&mut guess)
.read_line(&mut guess)
调用read_line
方法从便准输入句柄获取用户输入。将&mut guess
作为参数传递给read_line()
函数,将用户输入存储到这个字符串中。read_line()
的工作是,无论用户在标准输入中键入什么内容,都将其追加(不会覆盖其原有内容)到一个字符传中,因此它需要字符串作为参数。这个参数应该是可变的,以便read_line()
将用户输入附加上去。&
表示这个参数是一个引用(reference),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。Rust的一个主要优势就是安全而简单的操纵引用。引用默认情况是不可变的,因此需要写成&mut guess
来使其可变。
3.使用Result类型来处理潜在错误
.expect("Failed to read line");
read_line
会将用户输入附加到传递给它的字符串中,同时它也会返回一个类型为Result
的值。Result
是一种枚举类型(enum)。枚举类型变量的值是多种可能状态中的一个,每种可能的状态称为一种枚举成员
。Result
的成员是Ok
和Err
,Ok
表示成功,内部包含成功时产生的值;Err
表示失败,包含失败的前因后果。Result
的实例拥有expect()
方法。如果io::Result
实例的值是Err
,expect
会导致程序崩溃,并显示当作参数给expect
的信息。
4.使用println!占位符打印值
println!("You guessed: {guess}");
或
println!("You guessed: {}", guess);
第一个参数是格式化字符串,里面的{}
是预留在特定位置的占位符。使用{}
也可打印多个值:第一个{}
使用格式化字符串之后的第一个值,第二个{}
则使用第二个值。
六、生成一个秘密数字
Rust标准库尚未包含随机数功能,因此我们使用rand
使得每次产生不通的秘密数字。
1.使用crate来增加更多功能
crate
是一个Rust
代码包。crate
库可以包含任意能被其他使用的代码,但是不能自执行。
在Cargo.toml
导入rand
:
[package]
name = "guessing_game"
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.8.3"
[dependencies]
片段告诉Cargo本项目所依赖了哪些外部crate及其版本。0.8.3
事实上是^0.8.3
的简写,它表示任何至少是0.8.3
但小于0.9.0
的版本。
C:\Users\Xfsadmin\projects\guessing_game>cargo build
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.17
Compiling getrandom v0.2.8
Compiling rand_core v0.6.4
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
Compiling guessing_game v0.1.0 (C:\Users\Xfsadmin\projects\guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.17s
现在我们有了一个外部以来,Cargo从registry
上获取所有包的最新版本,这是一份来自Crates.io
的数据拷贝。Crates.io
是Rust生态环境中的开发者向他人贡献Rust开源项目的地方。
2.Cargo.lock文件确保构建是可重现的
Cargo有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo只会使用你指定的以来版本,除非你又手动制定了别的。
3.更新crate到一个新版本
当你确实需要升级crate时,Cargo提供了cargo update
命令,它会忽略Cargo.lock
文件,并计算出所有符合Cargo.toml
声明的最新版本。Cargo接下来会把这些版本写入Cargo.lock
文件。不过,Cargo默认智慧寻找大于0.8.3
而小于0.9.0
的版本(即默认升级不能跨越大版本)。
4.生成一个随机数
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
Rng
是一个trait
,它定义了随机数生成器应实现的方法,想要使用这些方法的话,此trait
必须在作用域中。
use rand::Rng;
这一行调用了rand::thread_rng
函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取seed
。接着调用随机数生成器的gen_range
方法。gen_range
方法获取一个范围表达式作为参数,并生成一个在此范围之间的随机数。这类范围表达式使用了start..=end
这样的形式,也就是说包含了上下断点,所需要执行1..=100
来请求1和100之间的数。
let secret_number = rand::thread_rng().gen_range(1..=100);
七、比较猜测的数字和秘密数字
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
增加了另一个use
声明,从标准库引入了一个叫做std::cmp::Ordering
的类型到作用域中。Ordering
也是一个枚举,不过它的成员是Less
、Greater
和Equal
。
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
新增的代码,它获取一个被比较值得引用:这里是把guess
与secert_number
作比较,然后它会返回一个刚才通过use
引入作用域的Ordering
枚举成员。使用match
表达式,根据对guess
和secret_number
调用cmp
返回的Ordering
成员来决定接下来做什么。
一个match
表达式由分支(arms)\构成。一个分支包含一个模式(pattern)\和表达式开头的值与分支模式相匹配时应该执行的代码。Rust获取提供给match
的值并逐一检查每个分支的模式。
C:\Users\Xfsadmin\projects\guessing_game>cargo build
Compiling guessing_game v0.1.0 (C:\Users\Xfsadmin\projects\guessing_game)
error[E0308]: mismatched types
--> src\main.rs:22:21
|
22 | match guess.cmp(&secret_number) {
| --- ^^^^^^^^^^^^^^ expected struct `String`, found integer
| |
| arguments to this function are incorrect
|
= note: expected reference `&String`
found reference `&{integer}`
note: associated function defined here
--> C:\Users\Xfsadmin\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\cmp.rs:789:8
|
789 | fn cmp(&self, other: &Self) -> Ordering;
| ^^^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `guessing_game` due to previous error
错误的核心表明这里由不匹配的类型(minsmatched types)。Rust有一个静态强类型系统,同时也有类型推断。当我们写出let guess = string::new()
时,Rust推断出guess
应该是String
类型,并不需要我们指定类型;另一方面secret_number
是数字类型,几个数字类型拥有1..=100
之间的值:32位数字i32
;32位无符号数字u32
;64位数字i64
/等等。Rust默认使用i32
,所以它是secret_number
的类型,除非增加类型信息,或任何能让Rust推断出不同类型的信息。因此错误的原因在于Rust不会比较字符串类型和数字类型。
所以我们必须把从输入中读取的String
转换为一个真正的数字类型,才好与秘密数字进行比较。我们在main
函数提供增加如下代码实现:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
这里增加:
let guess: u32 = guess.trim().parse().expect("Please type a number!");
Rust允许用一个新值来隐藏(shadow)guess
之前的值,这个功能常用在需要转换值类型之类的场景。它允许我们复用guess
变量的名字,而不是被迫创建两个不同变量。
我们将这个新变量绑定到guess.trim().parse()
表达式上。表达式中的guess
指的是包含输入的字符串类型guess
变量。String
实例的trim
方法会去除字符串开头和结尾的空白字符,我们必须执行此方法才能将字符串与u32
比较,因为u32
只能包含数值型数据。例如,当用户输入5
并按下enter
(在Windows上,按下enter
会得到一个回车符和一个换行符,\r\n
),guess
看起来就会像:5\n
或5\r\n
。因此trim
方法会消除\n
或\r\n
,只保留5
。
字符串的parse
方法将字符串转换成其他类型。这里是把字符串转换为数值。parse
方法只有在字符逻辑上可以转换为数字的时候才能工作,所以非常容易出错。parse
方法也会返回Result
类型。
八、使用循环来允许多次猜测
loop
关键字创建了一个无限循环。
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
}
九、猜测正确后退出
在代码中增加一行break
,用户猜对了什么数字后会推出循环。
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
十、处理无效输入
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
将expect
调用换成match
语句,从遇到错误就崩溃转换为处理错误。如果parse
能将字符转换为一个数字,它会返回一个包含数字结果的Ok
,这个Ok
值与match
第一个分支的模式相匹配,该分支对应的动作返回Ok
值中的数字num
,最后编程新创建的guess
变量;如果不能将字符串转换为数字,它会返回一个包含更多错误信息的Err
,Err
不会匹配match
第一个分支,但会匹配第二个分支Err(_)
模式:_
是一个通配符值,匹配所有。