Merge branch 'main' into patch-2

pull/509/head
Allan Downey 3 years ago
commit 48168d3168

@ -59,6 +59,7 @@
- [注释和文档](basic/comment.md) - [注释和文档](basic/comment.md)
- [格式化输出](basic/formatted-output.md) - [格式化输出](basic/formatted-output.md)
- [Rust 高级进阶](advance/intro.md) - [Rust 高级进阶](advance/intro.md)
- [生命周期](advance/lifetime/intro.md) - [生命周期](advance/lifetime/intro.md)
- [认识生命周期](advance/lifetime/basic.md) - [认识生命周期](advance/lifetime/basic.md)
- [深入生命周期](advance/lifetime/advance.md) - [深入生命周期](advance/lifetime/advance.md)
@ -155,7 +156,7 @@
- [通过 config.toml 对 Cargo 进行配置](cargo/reference/configuration.md) - [通过 config.toml 对 Cargo 进行配置](cargo/reference/configuration.md)
- [发布到 crates.io](cargo/reference/publishing-on-crates.io.md) - [发布到 crates.io](cargo/reference/publishing-on-crates.io.md)
- [构建脚本 build.rs](cargo/reference/build-script/intro.md) - [构建脚本 build.rs](cargo/reference/build-script/intro.md)
- [构建脚本示例 todo](cargo/reference/build-script/examples.md) - [构建脚本示例](cargo/reference/build-script/examples.md)
- [易混淆概念解析](confonding/intro.md) - [易混淆概念解析](confonding/intro.md)

@ -8,7 +8,7 @@
栈内存从高位地址向下增长,且栈内存是连续分配的,一般来说**操作系统对栈内存的大小都有限制**,因此 C 语言中无法创建任意长度的数组。在 Rust 中,`main` 线程的[栈大小是 `8MB`](https://course.rs/pitfalls/stack-overflow.html),普通线程是 `2MB`,在函数调用时会在其中创建一个临时栈空间,调用结束后 Rust 会让这个栈空间里的对象自动进入 `Drop` 流程,最后栈顶指针自动移动到上一个调用栈顶,无需程序员手动干预,因而栈内存申请和释放是非常高效的。 栈内存从高位地址向下增长,且栈内存是连续分配的,一般来说**操作系统对栈内存的大小都有限制**,因此 C 语言中无法创建任意长度的数组。在 Rust 中,`main` 线程的[栈大小是 `8MB`](https://course.rs/pitfalls/stack-overflow.html),普通线程是 `2MB`,在函数调用时会在其中创建一个临时栈空间,调用结束后 Rust 会让这个栈空间里的对象自动进入 `Drop` 流程,最后栈顶指针自动移动到上一个调用栈顶,无需程序员手动干预,因而栈内存申请和释放是非常高效的。
与栈相反,堆上内存则是从低位地址向上增长,**堆内存通常只受物理内存限制**,而且通常是不连续的,因此从性能的角度看,栈往往比堆更高。 与栈相反,堆上内存则是从低位地址向上增长,**堆内存通常只受物理内存限制**,而且通常是不连续的,因此从性能的角度看,栈往往比堆更高。
相比其它语言Rust 堆上对象还有一个特殊之处,它们都拥有一个所有者,因此受所有权规则的限制:当赋值时,发生的是所有权的转移(只需浅拷贝栈上的引用或智能指针即可),例如以下代码: 相比其它语言Rust 堆上对象还有一个特殊之处,它们都拥有一个所有者,因此受所有权规则的限制:当赋值时,发生的是所有权的转移(只需浅拷贝栈上的引用或智能指针即可),例如以下代码:
```rust ```rust
@ -183,6 +183,7 @@ fn main() {
那如果数组中每个元素都是一个 `Box` 对象呢?来看看 `Vec<Box<i32>>` 的内存布局: 那如果数组中每个元素都是一个 `Box` 对象呢?来看看 `Vec<Box<i32>>` 的内存布局:
```rust ```rust
(heap)
(stack) (heap) ┌───┐ (stack) (heap) ┌───┐
┌──────┐ ┌───┐ ┌─→│ 1 │ ┌──────┐ ┌───┐ ┌─→│ 1 │
│ vec2 │──→│B1 │─┘ └───┘ │ vec2 │──→│B1 │─┘ └───┘
@ -234,7 +235,7 @@ fn gen_static_str() -> &'static str{
} }
``` ```
在之前的代码中,如果 `String` 创建于函数中,那么返回它的唯一方法就是转移所有权给调用者 `fn move_str() -> String`,而通过 `Box::leak` 我们不仅返回了一个 `&str` 字符串切片,它还是 `'static` 类型的! 在之前的代码中,如果 `String` 创建于函数中,那么返回它的唯一方法就是转移所有权给调用者 `fn move_str() -> String`,而通过 `Box::leak` 我们不仅返回了一个 `&str` 字符串切片,它还是 `'static` 生命周期的!
要知道真正具有 `'static` 生命周期的往往都是编译期就创建的值,例如 `let v = "hello, world"`,这里 `v` 是直接打包到二进制可执行文件中的,因此该字符串具有 `'static` 生命周期,再比如 `const` 常量。 要知道真正具有 `'static` 生命周期的往往都是编译期就创建的值,例如 `let v = "hello, world"`,这里 `v` 是直接打包到二进制可执行文件中的,因此该字符串具有 `'static` 生命周期,再比如 `const` 常量。

@ -14,7 +14,7 @@ Rust 标准库中定义的那些智能指针,虽重但强,可以提供比引
智能指针往往是基于结构体实现,它与我们自定义的结构体最大的区别在于它实现了 `Deref``Drop` 特征: 智能指针往往是基于结构体实现,它与我们自定义的结构体最大的区别在于它实现了 `Deref``Drop` 特征:
- `Deref` 可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如 `*T` - `Deref` 可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如 `*T`
- `Drop` 允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作 - `Drop` 允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作
智能指针在 Rust 中很常见,我们在本章不会全部讲解,而是挑选几个最常用、最有代表性的进行讲解: 智能指针在 Rust 中很常见,我们在本章不会全部讲解,而是挑选几个最常用、最有代表性的进行讲解:

@ -7,7 +7,7 @@
上图并不能说 Rust 写的 `actix` 框架比 Go 的 `gin` 更好、更优秀,但是确实可以一定程度上说明 Rust 的异步性能非常的高! 上图并不能说 Rust 写的 `actix` 框架比 Go 的 `gin` 更好、更优秀,但是确实可以一定程度上说明 Rust 的异步性能非常的高!
简单来说,异步编程是一个[并发编程模型](https://course.rs/advance/concurrency-with-threads/concurrency-parallelism.html)目前主流语言基本都支持了当然支持的方式有所不同。异步编程允许我们同时并发运行大量的任务却仅仅需要几个甚至一个OS线程或CPU核心现代化的异步编程在使用体验上跟同步编程也几无区别例如 Go 语言的 `go` 关键字,也包括我们后面将介绍的 `async/await` 语法,该语法是 `Javascript` 和 `Rust` 的核心特性之一。 简单来说,异步编程是一个[并发编程模型](https://course.rs/advance/concurrency-with-threads/concurrency-parallelism.html)目前主流语言基本都支持了当然支持的方式有所不同。异步编程允许我们同时并发运行大量的任务却仅仅需要几个甚至一个OS线程或CPU核心现代化的异步编程在使用体验上跟同步编程也几无区别例如 Go 语言的 `go` 关键字,也包括我们后面将介绍的 `async/await` 语法,该语法是 `JavaScript` 和 `Rust` 的核心特性之一。
## async简介 ## async简介
`async` 是 Rust 选择的异步编程模型,下面我们来介绍下它的优缺点,以及何时适合使用。 `async` 是 Rust 选择的异步编程模型,下面我们来介绍下它的优缺点,以及何时适合使用。

@ -12,7 +12,7 @@ Rust 每个值都有其确切的数据类型,总的来说可以分为两类:
## 类型推导与标注 ## 类型推导与标注
与 Python、Javascript 等动态语言不同Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为 **Rust 编译器很聪明,它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在 [Rust 语言初印象](https://course.rs/first-try/hello-world.html#rust-语言初印象)中有过展示。 与 Python、JavaScript 等动态语言不同Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为 **Rust 编译器很聪明,它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在 [Rust 语言初印象](https://course.rs/first-try/hello-world.html#rust-语言初印象)中有过展示。
来看段代码: 来看段代码:

@ -211,7 +211,7 @@ fn main() {
let s1 = String::from("hello,"); let s1 = String::from("hello,");
let s2 = String::from("world!"); let s2 = String::from("world!");
// 在下句中s1的所有权被转移走了因此后面不能再使用s1 // 在下句中s1的所有权被转移走了因此后面不能再使用s1
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used let s3 = s1 + &s2;
assert_eq!(s3,"hello,world!"); assert_eq!(s3,"hello,world!");
// 下面的语句如果去掉注释,就会报错 // 下面的语句如果去掉注释,就会报错
// println!("{}",s1); // println!("{}",s1);

@ -4,7 +4,7 @@
- 前者从用户的角度出发来描述项目信息和依赖管理,因此它是由用户来编写 - 前者从用户的角度出发来描述项目信息和依赖管理,因此它是由用户来编写
- 后者包含了依赖的精确描述信息,它是由 `Cargo` 自行维护,因此不要去手动修改 - 后者包含了依赖的精确描述信息,它是由 `Cargo` 自行维护,因此不要去手动修改
它们的关系跟 `package.json``package-lock.json` 非常相似,从 Javascript 过来的同学应该会比较好理解。 它们的关系跟 `package.json``package-lock.json` 非常相似,从 JavaScript 过来的同学应该会比较好理解。
## 是否上传本地的 `Cargo.lock` ## 是否上传本地的 `Cargo.lock`
当本地开发时,`Cargo.lock` 自然是非常重要的,但是当你要把项目上传到 `Git` 时,例如 `Github`,那是否上传 `Cargo.lock` 就成了一个问题。 当本地开发时,`Cargo.lock` 自然是非常重要的,但是当你要把项目上传到 `Git` 时,例如 `Github`,那是否上传 `Cargo.lock` 就成了一个问题。

@ -1 +1,341 @@
# 构建脚本示例 todo # 构建脚本示例
下面我们通过一些例子来说明构建脚本该如何使用。社区中也提供了一些构建脚本的[常用功能](https://crates.io/keywords/build-dependencies),例如:
- [bindgen](https://crates.io/crates/bindgen), 自动生成 Rust -> C 的 FFI 绑定
- [cc](https://crates.io/crates/cc), 编译 C/C++/汇编
- [pkg-config](https://crates.io/crates/cc), 使用 `pkg-config` 工具检测系统库
- [cmake](https://crates.io/crates/cmake), 运行 `cmake` 来构建一个本地库
- [autocfg](https://crates.io/crates/autocfg), [rustc_version](https://crates.io/crates/rustc_version), [version_check](https://crates.io/crates/version_check),这些包提供基于 `rustc` 的当前版本来实现条件编译的方法
## 代码生成
一些项目需要在编译开始前先生成一些代码,下面我们来看看如何在构建脚本中生成一个库调用。
先来看看项目的目录结构:
```shell
.
├── Cargo.toml
├── build.rs
└── src
└── main.rs
1 directory, 3 files
```
`Cargo.toml` 内容如下:
```toml
# Cargo.toml
[package]
name = "hello-from-generated-code"
version = "0.1.0"
```
接下来,再来看看构建脚本的内容:
```rust
// build.rs
use std::env;
use std::fs;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("hello.rs");
fs::write(
&dest_path,
"pub fn message() -> &'static str {
\"Hello, World!\"
}
"
).unwrap();
println!("cargo:rerun-if-changed=build.rs");
}
```
以上代码中有几点值得注意:
- `OUT_DIR` 环境变量说明了构建脚本的输出目录,也就是最终生成的代码文件的存放地址
- 一般来说,构建脚本不应该修改 `OUT_DIR` 之外的任何文件
- 这里的代码很简单,但是我们这是为了演示,大家完全可以生成更复杂、更实用的代码
- `return-if-changed` 指令告诉 Cargo 只有在脚本内容发生变化时,才能重新编译和运行构建脚本。如果没有这一行,项目的任何文件发生变化都会导致 Cargo 重新编译运行该构建脚本
下面,我们来看看 `main.rs`
```rust
// src/main.rs
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
fn main() {
println!("{}", message());
}
```
这里才是体现真正技术的地方,我们联合使用 rustc 定义的 `include!` 以及 `concat!``env!` 宏,将生成的代码文件( `hello.rs` ) 纳入到我们项目的编译流程中。
例子虽然很简单,但是它清晰地告诉了我们该如何生成代码文件以及将这些代码文件纳入到编译中来,大家以后有需要只要回头看看即可。
## 构建本地库
有时,我们需要在项目中使用基于 C 或 C++ 的本地库,而这种使用场景恰恰是构建脚本非常擅长的。
例如,下面来看看该如何在 Rust 中调用 C 并打印 `Hello, World`。首先,来看看项目结构和 `Cargo.toml`:
```shell
.
├── Cargo.toml
├── build.rs
└── src
├── hello.c
└── main.rs
1 directory, 4 files
```
```toml
# Cargo.toml
[package]
name = "hello-world-from-c"
version = "0.1.0"
edition = "2021"
```
现在,我们还不会使用任何构建依赖,先来看看构建脚本:
```rust
// build.rs
use std::process::Command;
use std::env;
use std::path::Path;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
Command::new("gcc").args(&["src/hello.c", "-c", "-fPIC", "-o"])
.arg(&format!("{}/hello.o", out_dir))
.status().unwrap();
Command::new("ar").args(&["crus", "libhello.a", "hello.o"])
.current_dir(&Path::new(&out_dir))
.status().unwrap();
println!("cargo:rustc-link-search=native={}", out_dir);
println!("cargo:rustc-link-lib=static=hello");
println!("cargo:rerun-if-changed=src/hello.c");
}
```
首先,构建脚本将我们的 C 文件通过 `gcc` 编译成目标文件,然后使用 `ar` 将该文件转换成一个静态库,最后告诉 Cargo 我们的输出内容在 `out_dir` 中,编译器要在这里搜索相应的静态库,最终通过 `-l static-hello` 标志将我们的项目跟 `libhello.a` 进行静态链接。
但是这种硬编码的解决方式有几个问题:
- `gcc` 命令的跨平台性是受限的,例如 Windows 下就难以使用它,甚至于有些 Unix 系统也没有 `gcc` 命令,同样,`ar` 也有这个问题
- 这些命令往往不会考虑交叉编译的问题,如果我们要为 Android 平台进行交叉编译,那么 `gcc` 很可能无法输出一个 ARM 的可执行文件
但是别怕,构建依赖 `[build-dependencies]` 解君忧:社区中已经有现成的解决方案,可以让这种任务得到更容易的解决。例如文章开头提到的 [`cc`](https://crates.io/crates/cc) 包。首先在 `Cargo.toml` 中为构建脚本引入 `cc` 依赖:
```toml
[build-dependencies]
cc = "1.0"
```
然后重写构建脚本使用 `cc` :
```rust
// build.rs
fn main() {
cc::Build::new()
.file("src/hello.c")
.compile("hello");
println!("cargo:rerun-if-changed=src/hello.c");
}
```
不得不说Rust 社区的大腿就是粗,代码立刻简洁了很多,最重要的是:可移植性、稳定性等头疼的问题也得到了一并解决。
简单来说,`cc` 包将构建脚本使用 `C` 的需求进行了抽象:
- `cc` 会针对不同的平台调用合适的编译器windows 下调用 MSVC, MinGW 下调用 gcc Unix 平台调用 cc 等
- 在编译时会考虑到平台因素,例如将合适的标志传给正在使用的编译器
- 其它环境变量,例如 `OPT_LEVEL`、`DEBUG` 等会自动帮我们处理
- 标准输出和 `OUT_DIR` 的位置也会被 `cc` 所处理
如上所示,与其在每个构建脚本中复制粘贴相同的代码,将尽可能多的功能通过构建依赖来完成是好得多的选择。
再回到例子中,我们来看看 `src` 下的项目文件:
```c
// src/hello.c
#include <stdio.h>
void hello() {
printf("Hello, World!\n");
}
```
```rust
// src/main.rs
// 注意,这里没有再使用 `#[link]` 属性。我们把选择使用哪个 link 的责任交给了构建脚本,而不是在这里进行硬编码
extern { fn hello(); }
fn main() {
unsafe { hello(); }
}
```
至此,这个简单的例子已经完成,我们学到了该如何使用构建脚本来构建 C 代码,当然又一次被构建脚本和构建依赖的强大所震撼!但控制下情绪,因为构建脚本还能做到更多。
## 链接系统库
当一个 Rust 包想要链接一个本地系统库时,如何实现平台透明化,就成了一个难题。
例如,我们想使用在 Unix 系统中的 `zlib` 库,用于数据压缩的目的。实际上,社区中的 [`libz-sys`](https://crates.io/crates/libz-sys) 包已经这么做了,但是出于演示的目的,我们来看看该如何手动完成,当然,这里只是简化版的,想要看完整代码,见[这里](https://github.com/rust-lang/libz-sys)。
为了更简单的定位到目标库的位置,可以使用 [`pkg-config`](https://crates.io/crates/pkg-config) 包,该包使用系统提供的 `pkg-config` 工具来查询库的信息。它会自动告诉 Cargo 该如何链接到目标库。
先修改 `Cargo.toml`
```toml
# Cargo.toml
[package]
name = "libz-sys"
version = "0.1.0"
edition = "2021"
links = "z"
[build-dependencies]
pkg-config = "0.3.16"
```
这里的 `links = "z"` 用于告诉 Cargo 我们想要链接到 `libz` 库,在[下文](#使用其它-sys-包)还有更多的示例。
构建脚本也很简单:
```rust
// build.rs
fn main() {
pkg_config::Config::new().probe("zlib").unwrap();
println!("cargo:rerun-if-changed=build.rs");
}
```
下面再在代码中使用:
```rust
// src/lib.rs
use std::os::raw::{c_uint, c_ulong};
extern "C" {
pub fn crc32(crc: c_ulong, buf: *const u8, len: c_uint) -> c_ulong;
}
#[test]
fn test_crc32() {
let s = "hello";
unsafe {
assert_eq!(crc32(0, s.as_ptr(), s.len() as c_uint), 0x3610a686);
}
}
```
代码很清晰,也很简洁,这里就不再过多介绍,运行 [`cargo build --vv`](https://course.rs/cargo/reference/build-script/intro.html#构建脚本的输出) 来看看部分结果( 系统中需要已经安装 `libz` 库)
```shell
[libz-sys 0.1.0] cargo:rustc-link-search=native=/usr/lib
[libz-sys 0.1.0] cargo:rustc-link-lib=z
[libz-sys 0.1.0] cargo:rerun-if-changed=build.rs
```
非常棒,`pkg-config` 帮助我们找到了目标库,并且还告知了 Cargo 所有需要的信息!
实际使用中,我们需要做的比上面的代码更多,例如 [`libz-sys`](https://github.com/rust-lang/libz-sys) 包会先检查环境变量 `LIBZ_SYS_STATIC` 或者 `static` feature然后基于源码去构建 `libz`,而不是直接去使用系统库。
## 使用其它 sys 包
本例中,一起来看看该如何使用 `libz-sys` 包中的 `zlib` 来创建一个 C 依赖库。
若你有一个依赖于 `zlib` 的库,那可以使用 `libz-sys` 来自动发现或构建该库。这个功能对于交叉编译非常有用,例如 Windows 下往往不会安装 `zlib`
`libz-sys` 通过设置 [`include`](https://github.com/rust-lang/libz-sys/blob/3c594e677c79584500da673f918c4d2101ac97a1/build.rs#L156) 元数据来告知其它包去哪里找到 `zlib` 的头文件,然后我们的构建脚本可以通过 `DEP_Z_INCLUDE` 环境变量来读取 `include` 元数据( 关于元数据的传递,见[这里](https://course.rs/cargo/reference/build-script/intro.html#links) )。
```toml
# Cargo.toml
[package]
name = "zuser"
version = "0.1.0"
edition = "2021"
[dependencies]
libz-sys = "1.0.25"
[build-dependencies]
cc = "1.0.46"
```
通过包含 `libz-sys`,确保了最终只会使用一个 `libz` 库,并且给了我们在构建脚本中使用的途径:
```rust
// build.rs
fn main() {
let mut cfg = cc::Build::new();
cfg.file("src/zuser.c");
if let Some(include) = std::env::var_os("DEP_Z_INCLUDE") {
cfg.include(include);
}
cfg.compile("zuser");
println!("cargo:rerun-if-changed=src/zuser.c");
}
```
由于 `libz-sys` 帮我们完成了繁重的相关任务C 代码只需要包含 `zlib` 的头文件即可,甚至于它还能在没有安装 `zlib` 的系统上找到头文件:
```c
// src/zuser.c
#include "zlib.h"
// … 在剩余的代码中使用 zlib
```
## 条件编译
构建脚本可以通过发出 `rustc-cfg` 指令来开启编译时的条件检查。在本例中,一起来看看 [openssl](https://crates.io/crates/openssl) 包是如何支持多版本的 OpenSSL 库的。
`openssl-sys` 包对 OpenSSL 库进行了构建和链接,支持多个不同的实现(例如 LibreSSL )和多个不同的版本。它也使用了 `links` 配置,这样就可以给**其它构建脚本**传递所需的信息。例如 `version_number` ,包含了检测到的 OpenSSL 库的版本号信息。`openssl-sys` 自己的[构建脚本中](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl-sys/build/main.rs#L216)有类似于如下的代码:
```rust
println!("cargo:version_number={:x}", openssl_version);
```
该指令将 `version_number` 的信息通过环境变量 `DEP_OPENSSL_VERSION_NUMBER` 的方式传递给直接使用 `openssl-sys` 的项目。例如 `openssl` 包提供了更高级的抽象接口,并且它使用了 `openssl-sys` 作为依赖。`openssl` 的构建脚本会通过环境变量读取 `openssl-sys` 提供的版本号的信息,然后使用该版本号来生成一些 [`cfg`](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl/build.rs#L18-L36):
```rust
// (portion of build.rs)
if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
let version = u64::from_str_radix(&version, 16).unwrap();
if version >= 0x1_00_01_00_0 {
println!("cargo:rustc-cfg=ossl101");
}
if version >= 0x1_00_02_00_0 {
println!("cargo:rustc-cfg=ossl102");
}
if version >= 0x1_01_00_00_0 {
println!("cargo:rustc-cfg=ossl110");
}
if version >= 0x1_01_00_07_0 {
println!("cargo:rustc-cfg=ossl110g");
}
if version >= 0x1_01_01_00_0 {
println!("cargo:rustc-cfg=ossl111");
}
}
```
这些 `cfg` 可以跟 [`cfg` 属性]() 或 [`cfg` 宏]()一起使用以实现条件编译。例如,在 OpenSSL 1.1 中引入了 SHA3 的支持,那么我们就可以指定只有当版本号为 1.1 时,才[包含并编译相关的代码](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl/src/hash.rs#L67-L85):
```rust
// (portion of openssl crate)
#[cfg(ossl111)]
pub fn sha3_224() -> MessageDigest {
unsafe { MessageDigest(ffi::EVP_sha3_224()) }
}
```
当然,大家在使用时一定要小心,因为这可能会导致生成的二进制文件进一步依赖当前的构建环境。例如,当二进制可执行文件需要在另一个操作系统中分发运行时,那它依赖的信息对于该操作系统可能是不存在!

@ -114,7 +114,7 @@ $ cargo check
Finished dev [unoptimized + debuginfo] target(s) in 0.06s Finished dev [unoptimized + debuginfo] target(s) in 0.06s
``` ```
> Rust 虽然编译速度还行,但是还是不能 Go 语言相提并论,因为 Rust 需要做很多复杂的编译优化和语言特性解析,甚至连如何优化编译速度都成了一门学问 [优化编译速度](../profiling/compiler/speed-up.md) > Rust 虽然编译速度还行,但是还是不能 Go 语言相提并论,因为 Rust 需要做很多复杂的编译优化和语言特性解析,甚至连如何优化编译速度都成了一门学问: [优化编译速度](../profiling/compiler/speed-up.md)
## Cargo.toml 和 Cargo.lock ## Cargo.toml 和 Cargo.lock
@ -139,7 +139,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
``` ```
`name` 字段定义了项目名称,`version` 字段定义当前版本,新项目默认是 `0.1.0``edition` 字段定义了我们使用的 Rust 大版本。因为本书很新(不仅仅是现在新,未来也将及时修订,跟得上 Rust 的小步伐),所以使用的是 `Rust edition 2021` 大版本,详情见 [Rust 版本详解](../appendix/rust-version.md) `name` 字段定义了项目名称,`version` 字段定义当前版本,新项目默认是 `0.1.0``edition` 字段定义了我们使用的 Rust 大版本。因为本书很新(不仅仅是现在新,未来也将及时修订,跟得上 Rust 的小步伐),所以使用的是 `Rust edition 2021` 大版本,详情见 [Rust 版本详解](https://course.rs/appendix/rust-version.html)
### 定义项目依赖 ### 定义项目依赖
@ -161,10 +161,10 @@ color = { git = "https://github.com/bjz/color-rs" }
geometry = { path = "crates/geometry" } geometry = { path = "crates/geometry" }
``` ```
相信聪明的读者已经能看懂该如何引入外部依赖库,这里就不再赘述。详细的说明参见此章:[Cargo 依赖管理](../cargo/dependency.md),但是不建议大家现在去看,只要按照目录浏览,拨云见雾指日可待。 相信聪明的读者已经能看懂该如何引入外部依赖库,这里就不再赘述。详细的说明参见此章:[Cargo 依赖管理](https://course.rs/cargo/reference/specify-deps.html),但是不建议大家现在去看,只要按照目录浏览,拨云见雾指日可待。
## 基于 cargo 的项目组织结构 ## 基于 cargo 的项目组织结构
前文有提到 `cargo` 默认生成的项目结构,真实的项目肯定会有所不同,但是在目前的学习阶段,还无需关注。感兴趣的同学可以移步:[Cargo 项目结构](../cargo/guide/package-layout.md) 前文有提到 `cargo` 默认生成的项目结构,真实的项目肯定会有所不同,但是在目前的学习阶段,还无需关注。感兴趣的同学可以移步:[Cargo 项目结构](https://course.rs/cargo/guide/package-layout.html )
至此,大家对 Rust 项目的创建和管理已经有了初步的了解,那么来完善刚才的`"世界,你好"`项目吧。 至此,大家对 Rust 项目的创建和管理已经有了初步的了解,那么来完善刚才的`"世界,你好"`项目吧。

@ -1,9 +1,9 @@
# 优化编译速度 # 优化编译速度
<!--
https://www.reddit.com/r/rust/comments/rnkyc0/why_does_my_code_compile_faster_on_nightly/ https://www.reddit.com/r/rust/comments/rnkyc0/why_does_my_code_compile_faster_on_nightly/
https://www.reddit.com/r/rust/comments/rv8126/speedup_compilation_time/ https://www.reddit.com/r/rust/comments/rv8126/speedup_compilation_time/
https://www.reddit.com/r/rust/comments/rsfcgb/why_is_my_rust_build_so_slow/ https://www.reddit.com/r/rust/comments/rsfcgb/why_is_my_rust_build_so_slow/
https://www.reddit.com/r/rust/comments/sqi1ba/is_it_just_me_or_rustanalyzer_is_unreliableslow/ https://www.reddit.com/r/rust/comments/sqi1ba/is_it_just_me_or_rustanalyzer_is_unreliableslow/ -->

@ -60,7 +60,7 @@ Rust 是一门完全开源的语言,在 2015 年发布了 1.0 版本,但是
*https://medium.com/tenable-techblog/optimizing-700-cpus-away-with-rust-dc7a000dbdb2* *https://medium.com/tenable-techblog/optimizing-700-cpus-away-with-rust-dc7a000dbdb2*
Tenable 是一家网络安全解决方案提供商,它提供了一套可视化工具,并通过一个 `sidecar agent` 来过滤采集到的指标数据。最开始,该公司使用 Javascript 作为主要语言,当业务开始快速增长时,性能降级的问题就不可避免的发生了。 Tenable 是一家网络安全解决方案提供商,它提供了一套可视化工具,并通过一个 `sidecar agent` 来过滤采集到的指标数据。最开始,该公司使用 JavaScript 作为主要语言,当业务开始快速增长时,性能降级的问题就不可避免的发生了。
因此,在经过一系列调研后,`Tenable` 最终决定使用 Rust 来重写该服务,以获取更好的性能和安全性。最终结果也没有让他们失望,在延迟方面获得了 50% 的提升,如上图所示。 因此,在经过一系列调研后,`Tenable` 最终决定使用 Rust 来重写该服务,以获取更好的性能和安全性。最终结果也没有让他们失望,在延迟方面获得了 50% 的提升,如上图所示。

@ -1,6 +1,10 @@
# ChangeLog # ChangeLog
记录一些值得注意的变更。 记录一些值得注意的变更。
## 2022-03-03
- 新增章节: [Cargo - 构建脚本示例](https://course.rs/cargo/reference/build-script/examples.html)
## 2022-03-02 ## 2022-03-02
- 新增章节: [Cargo - 构建脚本](https://course.rs/cargo/reference/build-script/intro.html) - 新增章节: [Cargo - 构建脚本](https://course.rs/cargo/reference/build-script/intro.html)
Loading…
Cancel
Save