在 Linux 或 macOS 上安装 rustup
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh #下载
$ rustup update #更新
$ rustup self uninstall #卸载
使用 Cargo 创建项目
$ cargo new hello_cargo #创建项目
文件名: Cargo.toml
[package]
name = "hello_cargo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"
[dependencies]
[package]
,是一个片段(section)标题,表明下面的语句用来配置一个包
接下来的四行设置了 Cargo 编译程序所需的配置:项目的名称、版本、作者以及要使用的 Rust 版本。
[dependencies]
,是罗列项目依赖的片段的开始。
文件名:Cargo.lock
这个文件记录项目依赖的实际版本。这个项目并没有依赖,所以其内容比较少。你自己永远也不需要碰这个文件,让 Cargo 处理它就行了。
cargo build #默认编译debug版本,要编译release版加上--release
cargo check #快速检查代码确保其可以编译,并不产生可执行文件
cargo run #一步构建并运行项目
use std::io;
Rust 将 prelude 模块中少量的类型引入到每个程序的作用域中。如果需要的类型不在 prelude 中,你必须使用 use
语句显式地将其引入作用域。std::io
库提供很多有用的功能,包括接收用户输入的功能。
println!("Guess the number!");
println!
是一个在屏幕上打印字符串的宏
String::new();
,::
语法表明 new
是 String
类型的一个 关联函数,一些语言中把它称为 静态方法(static method)。
io::stdin().read_line(&mut guess).expect("Failed to read line");
stdin
函数返回一个 std::io::Stdin
的实例,这代表终端标准输入句柄的类型。.read_line(&mut guess)
,调用 read_line
方法从标准输入句柄获取用户输入,传递了一个参数:&mut guess
,&
表示这个参数是一个 引用(reference),mut
表示这个字符串参数应该是可变的,以便 read_line
将用户输入附加上去。
read_line
将用户输入附加到传递给它的字符串中,不过它也返回一个值——在这个例子中是 io::Result
。Rust 标准库中有很多叫做 Result
的类型:一个通用的 Result
以及在子模块中的特化版本,比如 io::Result
。Result
类型是 枚举(enumerations),通常也写作 enums。
Result
的成员是 Ok
和 Err
,Ok
成员表示操作成功,内部包含成功时产生的值。Err
成员则意味着操作失败,并且包含失败的前因后果。
println!("You guessed: {}", guess); // {}占位符
文件名: Cargo.toml
[dependencies]
rand = "0.5.5"
0.5.5
事实上是 ^0.5.5
的简写,它表示 “任何与 0.5.5 版本公有 API 相兼容的版本”。
有了一个外部依赖,Cargo 从 registry 上获取所有包的最新版本信息,这是一份来自 Crates.io 的数据拷贝。Crates.io 是 Rust 生态环境中的开发者们向他人贡献 Rust 开源项目的地方。
在更新完 registry 后,Cargo 检查 [dependencies]
片段并下载缺失的 crate 。本例中,虽然只声明了 rand
一个依赖,然而 Cargo 还是额外获取了 libc
和 rand_core
的拷贝,因为 rand
依赖 libc
和 rand_core
来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。
Cargo也是增量编译
Cargo.lock 文件确保构建是可重现的
这个问题的答案是 Cargo.lock 文件。它在第一次运行 cargo build
时创建,当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 Cargo.lock 文件。当将来构建项目时,Cargo 会发现 Cargo.lock 已存在并使用其中指定的版本。
当你 确实 需要升级 crate 时,Cargo 提供了另一个命令,update
,它会忽略 Cargo.lock 文件,并计算出所有符合 Cargo.toml 声明的最新版本。如果成功了,Cargo 会把这些版本写入 Cargo.lock 文件。
类似于git
的tag
记录版本,但可以使用update
更新Cargo.lock
Cargo 默认只会寻找大于 0.5.5
而小于 0.6.0
的版本。如果 rand
crate 发布了两个新版本,0.5.6
和 0.6.0
update
后使用的是0.5.6
而不是0.6.0
,想要使用 0.6.0
版本的 rand
或是任何 0.6.x
系列的版本,需要更新 Cargo.toml 文件:
[dependencies]
rand = "0.6.0"
下一次运行 cargo build
时,Cargo 会从 registry 更新可用的 crate,并根据你指定的新版本重新计算
use rand::Rng
。Rng
是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中
rand::thread_rng
函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。接下来,调用随机数生成器的 gen_range
方法。这个方法由刚才引入到作用域的 Rng
trait 定义。gen_range
方法获取两个数字作为参数,并生成一个范围在两者之间的随机数。它包含下限但不包含上限,所以需要指定 1
和 101
来请求一个 1 和 100 之间的数。
cargo doc --open
Cargo 有一个很棒的功能是:运行 cargo doc --open
命令来构建所有本地依赖提供的文档,并在浏览器中打开。
use std::cmp::Ordering;
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
从标准库引入了一个叫做 std::cmp::Ordering
的类型。同 Result
一样, Ordering
也是一个枚举,不过它的成员是 Less
、Greater
和 Equal
。这是比较两个值时可能出现的三种结果。
match
表达式由 分支(arms) 构成。一个分支包含一个 模式(pattern)和表达式开头的值与分支模式相匹配时应该执行的代码。
Rust 有一个静态强类型系统,同时也有类型推断。当我们写出 let guess = String::new()
时,Rust 推断出 guess
应该是 String
类型,并不需要我们写出类型。另一方面,secret_number
,是数字类型。几个数字类型拥有 1 到 100 之间的值:32 位数字 i32
;
$ cargo build
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
error[E0308]: mismatched types
--> src/main.rs:23:21
|
23 | match guess.cmp(&secret_number) {
| ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integer
|
= note: expected type `&std::string::String`
= note: found type `&{integer}`
error: aborting due to previous error
Could not compile `guessing_game`.
Rust 不会比较字符串类型和数字类型。
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Failed to read line");
let secret_number = rand::thread_rng().gen_range(1, 101);
let guess: u32 = guess.trim().parse().expect("Please type a number!");
Rust 允许用一个新值来 隐藏 (shadow) guess
之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用 guess
变量的名字,而不是被迫创建两个不同变量,诸如 guess_str
和 guess
之类。
guess
绑定到 guess.trim().parse()
表达式上。表达式中的 guess
是包含输入的原始 String
类型。String
实例的 trim
方法会去除字符串开头和结尾的空白字符。u32
只能由数字字符转换,不过用户必须输入 enter 键才能让 read_line
返回,然而用户按下 enter 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 5 并按下 enter,guess
看起来像这样:5
。
代表 “换行”,回车键。trim
方法消除
,只留下 5
。
loop
关键字创建了一个无限循环。
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
loop、continue和break
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
如果 parse
不 能将字符串转换为一个数字,它会返回一个包含更多错误信息的 Err
。Err
值不能匹配第一个 match
分支的 Ok(num)
模式,但是会匹配第二个分支的 Err(_)
模式:_
是一个通配符值,continue
意味着进入 loop
的下一次循环
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
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, 101);
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;
}
}
}
}