Rust是由Mozilla主导开发的通用、编译型编程语言。 设计准则为“安全、并发、实用”,支持函数式、並行式、程序式以及面向对象的编程风格 , 它有着惊人的运行速度(有些领域甚至超过 C/C++),能够防止运行错误,并保证线程安全。RUST 语言使每个人都能够构建可靠、高效的软件。
为了实现安全、速度和斌发行,它没有采用垃圾回收机制(GC)。这让它在其它语言并不擅长的场景中大展身手: 嵌入到其它语言中、在特定的时间和空间要求下编程、编写例如设备驱动和操作系统这样的底层代码。它通过一系列不产生运行时开销的编译时安全检查来提升目前语言所关注的领域,同时消除一切数据竞争。Rust 还致力于实现“零开销抽象”,虽然有些抽象看起来更像一个高级语言的特性。即便如此,你仍然可以使用 Rust 来做一些底层的精准控制。
学习网址 | 类型 |
---|---|
https://www.rust-lang.org/ | 官网 |
https://crates.io/ | 包管理平台 |
https://docs.rs/ | 文档中心 |
https://kaisery.github.io/trpl-zh-cn/ | 中文电子书 |
首先,需要安装最新版的 Rust 编译工具和 Visual Studio Code,具体编译工具以及Vs code安装教程自行百度。
Rust 编译工具:https://www.rust-lang.org/zh-CN/tools/install
Rust 的编译工具依赖 C 语言的编译工具,这意味着你的电脑上至少已经存在一个 C 语言的编译环境。如果你使用的是 Linux 系统,往往已经具备了 GCC 或 clang。如果你使用的是 macOS,需要安装 Xcode。如果你是用的是 Windows 操作系统,你需要安装 Visual Studio 2013 或以上的环境(需要 C/C++ 支持)以使用 MSVC 或安装 MinGW + GCC 编译环境(Cygwin 还没有测试)。
接下来,新建一个源文件,命名为 main.rs。Rust 源文件总是以 .rs 扩展名结尾。如果文件名包含多个单词,使用下划线分隔它们。例如命名为 hello_world.rs,而不是 helloworld.rs。
现在打开刚创建的 main.rs 文件,输入示例 代码。
文件名: main.rs
fn main() {
println!("Hello, world!");
}
保存文件,并回到终端窗口。在 Linux 或 macOS 上,输入如下命令,编译并运行文件:
$ rustc main.rs
$ ./main
Hello, world!
在 Windows 上,输入命令 .\main.exe
,而不是 ./main
:
> rustc main.rs
> .\main.exe
Hello, world!
不管使用何种操作系统,终端应该打印字符串 Hello, world!
。
如果出现以下错误提示,需要在 windows平台上先安装 Microsoft C++ build tools
error: linker `link.exe` not found
|
= note: 系统找不到指定的文件。 (os error 2)
note: the msvc targets depend on the msvc linker but `link.exe` was not found
note: please ensure that VS 2013, VS 2015, VS 2017 or VS 2019 was installed with the Visual C++ option
error: aborting due to previous error
点击 Microsoft C++ build tools,进行下载安装。
Visual Studio Code:https://code.visualstudio.com/Download
下载完 Visual Studio Code 安装包之后启动安装向导安装(此步骤不在此赘述)。
安装完 Visual Studio Code (下文简称 VSCode)之后运行 VSCode。使用快捷键ctrl+p ,输入内容 ext 后空格,按下Enter
拓展搜索框输入Chinese,安装简体中文扩展,使界面变成中文。
用同样的方法安装 rls 和 Native Debug 两个扩展。
重新启动 VSCode,Rust 的开发环境就搭建好了。
在D盘目录下创建Rust Workspace文件夹, 在 VSCode 中打开新建的文件夹:
打开新的终端
在终端中输入命令:
cargo new greeting
当前文件下下会构建一个名叫 greeting 的 Rust 工程目录。
现在在终端里输入以下三个命令 :
cd ./greeting
cargo build
cargo run
系统在创建工程时会生成一个 Hello, world 源程序 main.rs,这时会被编译并运行:
首先在刚才创建的工程下的main.rs下输入以下示例代码:
use std::io;
fn main() {
let hello=String::from("Hello,World! Hello, Rust!");
println!("{}",hello);
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);
}
接下来我们分析代码中的信息,猜猜看游戏的主要操作是用户提供输入,控制台反馈用户所猜的内容,那么我们需要将io
(输入/输出)库引入到当前的作用域,其中io
库来自标准库(也被称作std
)Rust将少量的类型引入到每个程序的作用域中,如果需要的类型不在模块中需要使用到use
语句来显式的将其引入作用域。
use std::id;
接下来 main
函数是程序的入口点:
fn main(){
其中fn
用来声明一个新的函数,()
表明没有参数,{
作为函数体的开始。
之后一系列的println!
表示是一个在屏幕上打印字符串的宏:
println!("Hello, world!");
println!("Guess the number !");
println!("Please input your guess.");
这些代码用于在控制台打印,提示并介绍游戏内容供用户输入。接下来创建一个存储用户输入的地方,像这样:
let mut guess=String::new();
我们可以看到这是一个let
语句,用来创建***变量*** (variable)。之所以let
后面多了一个修饰词mut
,是因为 在 Rust 中,变量默认是不可变的,在变量名前使用 mut
来使一个变量可变:
let a = 5; // 不可变
let mut b = 5; // 可变
让我们回到猜猜看程序中。现在我们知道了 let mut guess
会引入一个叫做 guess
的可变变量。等号(=
)的右边是 guess
所绑定的值,它是 String::new
的结果,这个函数会返回一个 String
的新实例。String
是一个标准库提供的字符串类型,它是 UTF-8 编码的可增长文本块。
::new
那一行的 ::
语法表明 new
是 String
类型的一个 关联函数(associated function)。关联函数是针对类型实现的,在这个例子中是 String
,而不是 String
的某个特定实例。一些语言中把它称为 静态方法(static method)。new
函数创建了一个新的空字符串,你会发现很多类型上有 new
函数,因为它是创建类型实例的惯用函数名。
总结一下,let mut guess = String::new();
这一行创建了一个可变变量,当前它绑定到一个新的 String
空实例上,接下来我们需要做的是获取到用户从键盘上输入的变量,于是调用io
库中的stdin
函数:
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
如果未在程序开头声明将io
库引入到程序中此时的io::stdin()
应该替换为std::io::stdin()
。代码的下一部分.readline(&mut guess)
方法从标准输入句柄中获取用户输入,我们还想read_line
传递了一个参数:&mut guess
。由于read_line
是无论用户在标准输入中输入什么内容,它都会将其存入到一个字符串中,所以它需要字符串作为参数,并且由于是通过用户键入赋值,所以将其设置为可变的。 &
表示这个参数是一个 引用(reference),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。引用是一个复杂的特性,Rust 的一个主要优势就是安全而简单的操纵引用。完成当前程序并不需要了解如此多细节。现在,我们只需知道它像变量一样,默认是不可变的。因此,需要写成 &mut guess
来使其可变,而不是 &guess
。
接下来的代码是使用 println!
占位符进行打印值
println!("You guessed: {}", guess);
这行代码打印存储用户输入的字符串。第一个参数是格式化字符串,里面的 {}
是预留在特定位置的占位符。使用 {}
也可以打印多个值:第一对 {}
使用格式化字符串之后的第一个值,第二对则使用第二个值,依此类推。调用一次 println!
打印多个值看起来像这样:
let x = 5;
let y = 10;
println!("x = {} and y = {}", x, y);
这行代码会打印出 x = 5 and y = 10
。当然也有更好的写法:
let x = 5;
let y = 10;
println!("x = {0} and y = {1}", x, y);
在 {}
之间可以放一个数字,它将把之后的可变参数当作一个数组来访问,下标从0
开始。
如果要输出 {
或 }
怎么办呢?格式字符串中通过{{
和}}
分别转义代表 {
和 }
。但是其他常用转义字符与 C 语言里的转义字符一样,都是反斜杠开头的形式。
fn main() {
println!("{{}}");
}
接下来,需要生成一个秘密数字,好让用户来猜。秘密数字应该每次都不同,这样重复玩才不会乏味;范围应该在 1 到 100 之间,这样才不会太困难。Rust 标准库中尚未包含随机数功能。然而,Rust 团队还是提供了一个 rand
库。 在我们使用 rand
编写代码之前,需要修改 Cargo.toml 文件,引入一个 rand
依赖。现在打开这个文件并在底部的 [dependencies]
片段标题之下添加:
[dependencies]
rand = "0.5.5"
在 Cargo.toml 文件中,标题以及之后的内容属同一个片段,直到遇到下一个标题才开始新的片段。[dependencies]
片段告诉 Cargo 本项目依赖了哪些外部 crate 及其版本。本例中,我们使用语义化版本 0.5.5
来指定 rand
库。Cargo 理解语义化版本(Semantic Versioning)(有时也称为 SemVer),这是一种定义版本号的标准。0.5.5
事实上是 ^0.5.5
的简写,它表示 “任何与 0.5.5 版本公有 API 相兼容的版本”。现在,不修改任何代码,构建项目,这时候的控制面板可能卡在Blocking waiting for file lock on package cache
通过百度查询建议可以设置cargo用的镜像:在你的CARGO_HOME目录下(默认是~/.cargo
)建立一个名叫config
没有扩展名【切记,没有扩展名!】的文件,内容如下:(有人的网络环境受限,没有办法以git协议访问外网,请把下一行里的git://
换成https://
或者http://
)
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'tuna'
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
验证:在命令行中运行 cargo install cargo-release,会成功安装一个小程序,第一次下载的时候会很慢,因为有大约四百兆的初始索引数据要下载。 现在,不修改任何代码,构建项目 :
$ cargo build
Updating crates.io index
Downloaded rand v0.5.5
Downloaded libc v0.2.62
Downloaded rand_core v0.2.2
Downloaded rand_core v0.3.1
Downloaded rand_core v0.4.2
Compiling rand_core v0.4.2
Compiling libc v0.2.62
Compiling rand_core v0.3.1
Compiling rand_core v0.2.2
Compiling rand v0.5.5
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53 s
Cargo.lock 文件确保构建是可重现的,这个问题的答案是 Cargo.lock 文件。它在第一次运行 cargo build
时创建,并放在 guessing_game 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 Cargo.lock 文件。当将来构建项目时,Cargo 会发现 Cargo.lock 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 0.5.5
直到你显式升级,多亏有了 Cargo.lock 文件。
接下来更新main.rs生成随机数
use std::io;
use rand::Rng;
fn main() {
let hello = String::from("Hello,World! Hello, Rust!");
println!("{}", hello);
let secret_number=rand::thread_rng().gen_range(1,101);
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);
println!("The secret number is: {}", secret_number);
}
首先,我们新增了一行 use
:use rand::Rng
。Rng
是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。第十章会详细介绍 trait。
接下来,我们在中间还新增加了两行。rand::thread_rng
函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。接下来,调用随机数生成器的 gen_range
方法。这个方法由刚才引入到作用域的 Rng
trait 定义。gen_range
方法获取两个数字作为参数,并生成一个范围在两者之间的随机数。它包含下限但不包含上限,所以需要指定 1
和 101
来请求一个 1 和 100 之间的数。 新增加的第二行代码打印出了秘密数字。
现在有了用户输入和一个随机数,我们可以比较它们。
/*
*Rust 有一个静态强类型系统,同时也有类型推断。当我们写出 let guess = String::new() 时,
*Rust 推断出 guess 应该是 String 类型,并不需要我们写出类型。另一方面,secret_number,
*是数字类型。几个数字类型拥有 1 到 100 之间的值:32 位数字 i32;32 位无符号数字 u32;
*64 位数字 i64 等等。Rust 默认使用 i32,所以它是 secret_number 的类型,除非增加类型信息,
*或任何能让 Rust 推断出不同数值类型的信息,所以通过以下方法来转换为数字类型
*/
let guess: u32 = guess.trim().parse().expect("Please type a number!");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
不过等等,不是已经有了一个叫做 guess
的变量了吗?确实如此,不过 Rust 允许用一个新值来 隐藏 (shadow) guess
之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用 guess
变量的名字,而不是被迫创建两个不同变量, 我们将 guess
绑定到 guess.trim().parse()
表达式上。表达式中的 guess
是包含输入的原始 String
类型。String
实例的 trim
方法会去除字符串开头和结尾的空白字符。u32
只能由数字字符转换,不过用户必须输入 enter 键才能让 read_line
返回,然而用户按下 enter 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 5 并按下 enter,guess
看起来像这样:5\n
。\n
代表 “换行”,回车键。trim
方法消除 \n
,只留下 5
。字符串的 parse
方法将字符串解析成数字。因为这个方法可以解析多种数字类型,因此需要告诉 Rust 具体的数字类型,这里通过 let guess: u32
指定。guess
后面的冒号(:
)告诉 Rust 我们指定了变量的类型。Rust 有一些内建的数字类型;u32
是一个无符号的 32 位整型。对于不大的正整数来说,它是不错的类型,第三章还会讲到其他数字类型。另外,程序中的 u32
注解以及与 secret_number
的比较,意味着 Rust 会推断出 secret_number
也是 u32
类型。现在可以使用相同类型比较两个值了!
parse
调用很容易产生错误。例如,字符串中包含 A%
,就无法将其转换为一个数字。因此,parse
方法返回一个 Result
类型。像之前 “使用 Result
类型来处理潜在的错误“讨论的 read_line
方法那样,再次按部就班的用 expect
方法处理即可。如果 parse
不能从字符串生成一个数字,返回一个 Result
的 Err
成员时,expect
会使游戏崩溃并打印附带的信息。如果 parse
成功地将字符串转换为一个数字,它会返回 Result
的 Ok
成员,然后 expect
会返回 Ok
值中的数字
现在游戏已经大体上能玩了,不过用户只能猜一次。增加一个循环来改变它吧! loop
关键字创建了一个无限循环。将其加入后,用户可以反复猜测, 用户总能使用 ctrl-c 终止程序。
loop {
println!("Please input your guess.");
let mut guess = String::new();
/*
*获取用户输入
*/
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
/*
*Rust 有一个静态强类型系统,同时也有类型推断。当我们写出 let guess = String::new() 时,
*Rust 推断出 guess 应该是 String 类型,并不需要我们写出类型。另一方面,secret_number,
*是数字类型。几个数字类型拥有 1 到 100 之间的值:32 位数字 i32;32 位无符号数字 u32;
*64 位数字 i64 等等。Rust 默认使用 i32,所以它是 secret_number 的类型,除非增加类型信息,
*或任何能让 Rust 推断出不同数值类型的信息,所以通过以下方法来转换为数字类型
*/
let guess: u32 = guess.trim().parse().expect("Please type a number!");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
println!("You guessed: {}", guess);
println!("The secret number is: {}", secret_number);
}
让我们增加一个 break
语句,在用户猜对时退出游戏:
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
},
}
通过在 You win!
之后增加一行 break
,用户猜对了神秘数字后会退出循环。退出循环也意味着退出程序,因为循环是 main
的最后一部分。
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
let hello = String::from("Hello,World! Hello, Rust!");
println!("{}", hello);
/*
*生成随机数
*/
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("Guess the number !");
loop {
println!("Please input your guess.");
let mut guess = String::new();
/*
*获取用户输入
*/
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
/*
*Rust 有一个静态强类型系统,同时也有类型推断。当我们写出 let guess = String::new() 时,
*Rust 推断出 guess 应该是 String 类型,并不需要我们写出类型。另一方面,secret_number,
*是数字类型。几个数字类型拥有 1 到 100 之间的值:32 位数字 i32;32 位无符号数字 u32;
*64 位数字 i64 等等。Rust 默认使用 i32,所以它是 secret_number 的类型,除非增加类型信息,
*或任何能让 Rust 推断出不同数值类型的信息,所以通过以下方法来转换为数字类型
*/
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
/*
*比较用户输入与随机生成
*/
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
println!("You guessed: {}", guess);
println!("The secret number is: {}", secret_number);
}
}
本章通过简略介绍Rust、环境搭建以及动手实践,向你介绍了 Rust 新概念:let
、match
、方法、关联函数、使用外部 资源库 等等,这样我们就算初步认识Rust了为后续学习Rust打定了基础。