Skip to content

简介

《Rust 编程语言》

《Rust 高级编程》

《Rust 异步编程》

Rust 技术论坛

https://doc.rust-lang.org/reference/introduction.html

官方文档

https://docs.rs/

GitHub

https://www.rust-lang.org/zh-CN/

https://kaisery.github.io/trpl-zh-cn/

https://rust.cc/

https://rustwiki.org/zh-CN/

https://github.com/rust-unofficial/awesome-rust

尝试 Rust

在线书籍

《Rust 程序设计语言》

《Rust中文宝典》

《通过例子学 Rust》

缘起

任何一门新技术的兴起,都是为了解决一个问题

自操作系统诞生以来,系统级主流编程语言,从汇编语言到 C++,已经发展了近 50 个年头,但仍然存在两个难题

  • 很难编写内存安全的代码
  • 很难编写线程安全的代码

这两个难题存在的本质原因是 C/C++ 属于类型不安全的语言,它们薄弱的内存管理机制导致了很多常见的漏洞。
其实 20 世纪 80 年代也出现过非常优秀的语言,比如 Ada 语言。Ada 拥有诸多优秀的特性:可以在编译期进行类型检查、无 GC 式确定性内存管理、内置安全并发模型、无数据竞争、系统级硬实时编程等。
但它的性能和同时期的 C/C++ 相比确实是有差距的。那个时代计算资源匮乏,大家追求的是性能。
所以,大家都宁愿牺牲安全性来换取性能。这也是 C/C++ 得以普及的原因。

到了 2006 年,自称“职业编程语言工程师”的 Graydon Hoare(简称为 GH),开始开发一门名为 Rust 的编程语言。

什么是“职业编程语言工程师”?用 GH 自己的话说,职业编程工程师的日常工作就是给其他语言开发编译器和工具集,但并未参与这些语言本身的设计。
自然而然地,GH 萌生了自己开发一门语言的想法,这门语言就是 Rust

"Rust" 这个名字包含了 GH 对这门语言的预期。
在自然界有一种叫做锈菌(Rust Fungi)的真菌,这种真菌寄生于植物中,引发病害,而且号称“本世纪最可怕的生态病害”之一。
这种真菌的生命力非常顽强,其在生命周期内可以产生 5 种孢子类型,这 5 种生命形态还可以相互转化,如果用软件术语来描述这种特性,那就是“鲁棒性超强”。
回想一下 Rust 的 Logo 形状,像不像一个细菌?Logo 上面有 5 个圆圈,也和锈菌这 5 种生命形态相对应,暗示了 Rust 语言的鲁棒性也超强。
"Rust" 也有“铁锈”的意思,暗合“裸金属”之意,代表了 Rust 的系统级编程语言属性,有直接操作底层硬件的能力。
此外,“Rust”在字形组合上也糅合了"Trust"和"Robust",暗示了“信任”与“鲁棒性”。
因此,“Rust”真可谓一个好名字。事实证明,Rust 语言不仅仅是名字起得好。

Rust 是一门同时追求安全、并发和性能的现代系统级编程语言。

设计哲学

Rust 语言遵循了三条设计哲学:

  • 内存安全
  • 零成本抽象
  • 实用性

内存安全

现代编程语言早已发展到了“程序即类型证明”的阶段,类型系统基本已经成为了各大编程语言的标配。
类型系统提供了以下好处:

  • 允许编译器侦测无意义甚至无效的代码,暴露程序中隐含的错误
  • 可以为编译器提供有意义的类型信息,帮助优化代码
  • 可以增强代码的可读性,更直白地阐述开发者的意图
  • 提供了一定程度的高级抽象,提升开发效率

一般来说,一门语言只要保证类型安全,就可以说它是一门安全的语言。
简单来说,类型安全是指类型系统可以保证程序的行为是意义明确、不出错的。
像 C/C++ 语言的类型系统就不是类型安全的,因为它们并没有对无意义的行为进行约束。
一个最简单的例子就是数组越界,在 C/C++ 语言中并不对其做任何检查,导致发生了语言规范规定之外的行为,也即是未定义行为
而这些未定义行为恰恰是漏洞的温床。所以,像 C/C++ 这种语言就是类型不安全的语言。

Rust 语言如果想保证内存安全,首先要做的就是保证类型安全。

什么是内存安全呢?简单来说,就是不会出现内存访问错误

只有当程序访问未定义内存的时候才会产生内存错误。一般来说,发生以下几种情况就会产生内存错误:

  • 引用空指针
  • 使用未初始化内存
  • 释放后使用,也就是使用悬垂指针
  • 缓冲区溢出,比如数组越界
  • 非法释放已经释放过的指针或未分配的指针,也就是重复释放

这些情况之所以会产生内存错误,是因为它们都访问了未定义内存。
为了保证内存安全,Rust 语言建立了严格的安全内存管理模型

  • 所有权系统。每个被分配的内存都有一个独占其所有权的指针。只有当该指针被销毁时,其对应的内存才能随之被释放
  • 借用和生命周期。每个变量都有其生命周期,一旦超出生命周期,变量就会被自动释放。如果是借用,则可以通过标记生命周期参数供编译器检查的方式,防止出现悬垂指针,也就是释放后使用的情况。

其中所有权系统还包括了从现代 C++ 那里借鉴的 RAII 机制,这是 Rust 无 GC 但是可以安全管理内存的基石。

建立了安全内存管理模型之后,再用类型系统表达出来即可。
Rust 从 Haskell 的类型系统那里借鉴了以下特性:

  • 没有空指针
  • 默认不可变
  • 表达式
  • 高阶函数
  • 代数数据类型
  • 模式匹配
  • 泛型
  • trait 和关联类型
  • 本地类型推导

为了实现内存安全,Rust 还具备以下独有的特性:

  • 仿射类型(Affine Type),该类型用来表达 Rust 所有权中的 Move 语义
  • 借用、生命周期

借助类型系统的强大,Rust 编译器可以在编译期对类型进行检查,看其是否满足安全内存模型,在编译期就能发现内存不安全问题,有效地阻止未定义行为的发生。

内存安全的 bug 和并发安全的 bug 产生的内在原因是相同的,都是因为内存的不正当访问而造成的。
同样,利用装载了所有权的强大类型系统,Rust 还解决了并发安全的问题。Rust 编译器会通过静态检查分析,在编译期就检查出多线程并发代码中所有的数据竞争问题

零成本抽象

除了安全性,Rust 还追求高效开发和性能
编程语言如果想做到高效开发,就必须拥有一定的抽象表达能力。

Rust 的抽象是零成本的,Rust 的抽象并不会存在运行时性能开销,这一切都是在编译期完成的。

Rust 中零成本抽象的基石就是泛型和 trait

实用性

目前大部分开发者认为 Rust 语言学习曲线颇陡。其中最为诟病的就是 Rust 目前的借用检查系统。

要解决这个问题,可以从以下几点出发来学习 Rust:

  • 保持初学者心态。当面对 Rust 中难以理解的概念时,先不要急于把其他语言的经验套用其上,而应该从 Rust 的设计哲学出发,去理解如此设计 Rust 的语言特性的原因,寻找其内在的一致性。
  • 先学习概念再动手实践。很多传统语言开发者在学习 Rust 的时候,一上来就开始动手写代码,结果却栽了跟头,连编译都无法通过。看似符合直觉的代码,却因为借用检查而导致编译失败。这是因为 Rust 编译器在你编写代码中发现了隐藏的错误,而你却未察觉。所以,其实不是 Rust 学习曲线陡峭,而是直接动手写代码的学习方法有问题。
  • 把编译器当作朋友。不要忽略 Rust 编译器的诊断信息,大多数情况下,这些诊断信息里已经把错误原因阐述得非常明确。这些诊断信息可以帮助你学习 Rust,纠正自己的错误认知

俗话说得好,逆境也是机遇。正是因为 Rust 有这些特点,学习 Rust 的过程也是一次自我提升的过程,能够帮助我们成为更好的程序员

Rust 语言解决了内存安全和并发安全的问题,可以极大地提升软件的质量。Rust 的诞生为业界提供了一个除 C 和 C++ 之外的更好的选择。
因为 Rust 是对安全、并发和性能都很看重的语言,它可以用于嵌入式系统、操作系统、网络服务等底层系统,但它并不局限于此,它还可以用于开发上层 Web 应用、游戏引擎和机器学习,甚至基于 WebAssembly 技术还可以开发前端组件。
因为高的安全性和不逊于 C/C++ 的性能,Rust 也被应用于新的前沿领域,比如区块链技术

安装

brew install rustup

linux 安装:

1
2
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env

国内安装

https://rustcc.com/article?id=6baecc6f-29ed-40d2-870b-343606aa2e93

https://www.cnblogs.com/hustcpp/p/12341098.html

https://mirrors.ustc.edu.cn/help/crates.io-index.html

nightly 版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
➜  ~ rustc --version
rustc 1.48.0 (7eac88abb 2020-11-16)

➜  ~ rustup update nightly
➜  ~ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

➜  ~ rustup default nightly-x86_64-unknown-linux-gnu
➜  ~ rustc --version
rustc 1.51.0-nightly (158f8d034 2020-12-29)
➜  ~ rustup toolchain list
stable-x86_64-unknown-linux-gnu
nightly-x86_64-unknown-linux-gnu (default)

rustup

https://github.com/rust-lang/rustup/blob/master/README.md

报错:

1
2
➜  ~ rustc --version
error: no default toolchain configured

解决方法:rustup install stable
失败了就重新再下,会接着上次的继续下载,然后直到下完(摊手🤷‍♂️

最后执行rustc --version命令成功则说明安装成功

1
2
➜  ~ rustc --version
rustc 1.39.0 (4560ea788 2019-11-04)

升级rust: rustup update stable

rust 更新到最新 nightly 版本

1
2
3
4
5
6
7
8
9
rustup update nightly
rustup toolchain list

stable-x86_64-unknown-linux-gnu
nightly-2020-04-10-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

rustup default nightly-x86_64-unknown-linux-gnu
rustc 1.49.0-nightly (8dae8cdcc 2020-10-12)

但是上面这种更新方式的版本可能不太完整,需要通过下面的网址查看完整版本

https://rust-lang.github.io/rustup-components-history/

例如完整版本为:nightly-2020-10-13,则执行:rustup toolchain add nightly-2020-10-13

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
rustup toolchain add nightly-2020-10-13
rustup default nightly-2020-10-13-x86_64-unknown-linux-gnu

rustup toolchain list        

stable-x86_64-unknown-linux-gnu
nightly-2020-04-10-x86_64-unknown-linux-gnu
nightly-2020-10-13-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

rustup uninstall nightly-2020-04-10-x86_64-unknown-linux-gnu

第一个程序

main.rs:

1
2
3
fn main() {
    println!("Hello world!");
}

编译:rustc main.rs
运行:./main,最后在终端输出Hello, world!

println! 调用了一个 Rust 宏(macro)。如果是调用函数,则应输入 println(没有!

Cargo

Cargo 是 Rust 的构建系统和包管理器。
大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库并编译这些库。(我们把代码所需要的库叫做依赖)

1
2
➜  ~ cargo --version
cargo 1.39.0 (1c6ec66d5 2019-09-30)

创建项目

1
2
$ cargo new hello_cargo
$ cd hello_cargo

第一行命令新建了名为 hello_cargo 的目录。我们将项目命名为 hello_cargo,同时 Cargo 在一个同名目录中创建项目文件。

进入 hello_cargo 目录并列出文件。将会看到 Cargo 生成了两个文件和一个目录:一个 Cargo.toml 文件,一个 src 目录,以及位于 src 目录中的 main.rs 文件。它也在 hello_cargo 目录初始化了一个 git 仓库,以及一个 .gitignore 文件。

文件名:Cargo.toml

1
2
3
4
5
6
7
8
9
[package]
name = "hello_cargo"
version = "0.1.0"
authors = ["zhaoyz <954241552@qq.com>"]
edition = "2018"

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

[dependencies]

这个文件使用 TOML (Tom's Obvious, Minimal Language) 格式,这是 Cargo 配置文件的格式。

第一行,[package],是一个片段(section)标题,表明下面的语句用来配置一个包。随着我们在这个文件增加更多的信息,还将增加其他片段(section)。

接下来的四行设置了 Cargo 编译程序所需的配置:项目的名称、版本、作者以及要使用的 Rust 版本。

最后一行,[dependencies],是罗列项目依赖的片段的开始。在 Rust 中,代码包被称为 crates。这个项目并不需要其他的 crate

文件名: src/main.rs

1
2
3
fn main() {
    println!("Hello, world!");
}

构建并运行项目

1
2
3
➜  hello_cargo git:(master) ✗ cargo build
   Compiling hello_cargo v0.1.0 (/Users/nocilantro/Desktop/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 3.60s

这个命令会创建一个可执行文件 target/debug/hello_cargo, 而不是放在目前目录下。可以通过这个命令运行可执行文件:

1
2
➜  hello_cargo git:(master) ✗ ./target/debug/hello_cargo
Hello, world!

首次运行 cargo build 时,也会使 Cargo 在项目根目录创建一个新文件:Cargo.lock
这个文件记录项目依赖的实际版本。这个项目并没有依赖,所以其内容比较少。你自己永远也不需要碰这个文件,让 Cargo 处理它就行了。

我们刚刚使用 cargo build 构建了项目,并使用 ./target/debug/hello_cargo 运行了程序,也可以使用 cargo run 在一个命令中同时编译并运行生成的可执行文件:

1
2
3
4
➜  hello_cargo git:(master) ✗ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/hello_cargo`
Hello, world!

注意这一次并没有出现表明 Cargo 正在编译 hello_cargo 的输出。Cargo 发现文件并没有被改变,就直接运行了二进制文件。如果修改了源文件的话,Cargo 会在运行之前重新构建项目,并会出现像这样的输出:

1
2
3
4
5
➜  hello_cargo git:(master) ✗ cargo run
   Compiling hello_cargo v0.1.0 (/Users/nocilantro/Desktop/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 1.24s
     Running `target/debug/hello_cargo`
Hello, world!!!

Cargo 还提供了一个叫 cargo check 的命令。该命令快速检查代码确保其可以编译,但并不产生可执行文件:

1
2
3
➜  hello_cargo git:(master) ✗ cargo check
    Checking hello_cargo v0.1.0 (/Users/nocilantro/Desktop/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.74s

通常 cargo check 要比 cargo build 快得多,因为它省略了生成可执行文件的步骤。
如果你在编写代码时持续的进行检查,cargo check 会加速开发!
为此很多 Rustaceans 编写代码时定期运行 cargo check 确保它们可以编译。当准备好使用可执行文件时才运行 cargo build

cargo下载速度太慢

http://mirrors.ustc.edu.cn/help/crates.io-index.html

$HOME/.cargo/config中添加如下内容:

1
2
3
4
5
[source.crates-io]
replace-with = "rustcc"

[source.rustcc]
registry = "git://crates.rustcc.cn/crates.io-index"

或者下面这个(貌似没有上面这个稳定)

1
2
3
4
5
[source.crates-io]
replace-with = 'ustc'

[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

Rust国内镜像源大全

可以对 config 进行如下配置,切换源时只需要改动 replace-with = xxx 即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[source.crates-io]
replace-with = "ustc"

[source.rustcc]
registry = "git://crates.rustcc.cn/crates.io-index"

[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"

[net]
git-fetch-with-cli = true

vscode 插件

1
2
3
4
5
rustup component add rust-src
rustup component add rustfmt
rustup component add clippy
rustup component add rust-analysis
rustup component add rls

安装(Rust - Visual Studio Marketplace)

这一个插件应该就够了

cargo 下载出现问题

Blocking waiting for file lock on package cache:

解决办法:

rm ~/.cargo/.package-cache
然后重新下载

warning: spurious network error (2 tries remaining): [7] Couldn't connect to server (Failed to connect to crates-io.proxy.ustclug.org port 443: Connection timed out):

解决办法:
修改$HOME/.cargo/config中内容:

1
2
3
4
5
[source.crates-io]
replace-with = 'ustc'

[source.ustc]
registry = "https://mirrors.ustc.edu.cn/crates.io-index"

离线文档

执行rustup docs

file:///Users/nocilantro/.rustup/toolchains/stable-x86_64-apple-darwin/share/doc/rust/html/std/index.html

file:///Users/nocilantro/.rustup/toolchains/nightly-x86_64-apple-darwin/share/doc/rust/html/std/index.html

离线文档首页:

file:///Users/nocilantro/.rustup/toolchains/nightly-x86_64-apple-darwin/share/doc/rust/html/index.html