简介
https://doc.rust-lang.org/reference/introduction.html
https://www.rust-lang.org/zh-CN/
https://kaisery.github.io/trpl-zh-cn/
https://github.com/rust-unofficial/awesome-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 |
|
国内安装
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 |
|
rustup
https://github.com/rust-lang/rustup/blob/master/README.md
报错:
1 2 |
|
解决方法:rustup install stable
失败了就重新再下,会接着上次的继续下载,然后直到下完(摊手🤷♂️
最后执行rustc --version
命令成功则说明安装成功
1 2 |
|
升级rust
: rustup update stable
rust 更新到最新 nightly 版本
1 2 3 4 5 6 7 8 9 |
|
但是上面这种更新方式的版本可能不太完整,需要通过下面的网址查看完整版本
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 |
|
第一个程序
main.rs
:
1 2 3 |
|
编译:rustc main.rs
运行:./main
,最后在终端输出Hello, world!
println!
调用了一个 Rust 宏(macro)。如果是调用函数,则应输入 println
(没有!
)
Cargo
Cargo 是 Rust 的构建系统和包管理器。
大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库并编译这些库。(我们把代码所需要的库叫做依赖)
1 2 |
|
创建项目
1 2 |
|
第一行命令新建了名为 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 |
|
这个文件使用 TOML (Tom's Obvious, Minimal Language) 格式,这是 Cargo 配置文件的格式。
第一行,[package]
,是一个片段(section)标题,表明下面的语句用来配置一个包。随着我们在这个文件增加更多的信息,还将增加其他片段(section)。
接下来的四行设置了 Cargo 编译程序所需的配置:项目的名称、版本、作者以及要使用的 Rust 版本。
最后一行,[dependencies]
,是罗列项目依赖的片段的开始。在 Rust 中,代码包被称为 crates。这个项目并不需要其他的 crate
文件名: src/main.rs
1 2 3 |
|
构建并运行项目
1 2 3 |
|
这个命令会创建一个可执行文件 target/debug/hello_cargo
, 而不是放在目前目录下。可以通过这个命令运行可执行文件:
1 2 |
|
首次运行 cargo build
时,也会使 Cargo 在项目根目录创建一个新文件:Cargo.lock
。
这个文件记录项目依赖的实际版本。这个项目并没有依赖,所以其内容比较少。你自己永远也不需要碰这个文件,让 Cargo 处理它就行了。
我们刚刚使用 cargo build
构建了项目,并使用 ./target/debug/hello_cargo
运行了程序,也可以使用 cargo run
在一个命令中同时编译并运行生成的可执行文件:
1 2 3 4 |
|
注意这一次并没有出现表明 Cargo 正在编译 hello_cargo 的输出。Cargo 发现文件并没有被改变,就直接运行了二进制文件。如果修改了源文件的话,Cargo 会在运行之前重新构建项目,并会出现像这样的输出:
1 2 3 4 5 |
|
Cargo 还提供了一个叫 cargo check
的命令。该命令快速检查代码确保其可以编译,但并不产生可执行文件:
1 2 3 |
|
通常 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 |
|
或者下面这个(貌似没有上面这个稳定)
1 2 3 4 5 |
|
可以对 config 进行如下配置,切换源时只需要改动 replace-with = xxx
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
vscode 插件
1 2 3 4 5 |
|
安装(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 |
|
离线文档
执行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