diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..3f24c59a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + test: + name: test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: | + rustup set profile minimal + rustup toolchain install stable + rustup default stable + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v2 + with: + mdbook-version: "latest" + - name: Run tests + run: mdbook test diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..be43ece4 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: Deploy + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +defaults: + run: + shell: bash + +permissions: + contents: write + +jobs: + deploy: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: | + rustup set profile minimal + rustup toolchain install stable + rustup default stable + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v2 + with: + mdbook-version: "latest" + - run: mdbook build + - name: Copy Assets + run: | + chmod +x ci/copy-assets.sh + ci/copy-assets.sh ${{ matrix.os }} + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: ${{ github.ref == 'refs/heads/main' }} + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./book diff --git a/ci/copy-assets.sh b/ci/copy-assets.sh new file mode 100644 index 00000000..c29260ee --- /dev/null +++ b/ci/copy-assets.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cp ./assets/CNAME ./book/ +cp ./assets/*.html ./book/ +cp ./assets/sitemap.xml ./book/ \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 37f4e711..ea23eabc 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -384,3 +384,6 @@ - [1.75](appendix/rust-versions/1.75.md) - [1.76](appendix/rust-versions/1.76.md) - [1.77](appendix/rust-versions/1.77.md) + - [1.78](appendix/rust-versions/1.78.md) + - [1.79](appendix/rust-versions/1.79.md) + - [1.80](appendix/rust-versions/1.80.md) diff --git a/src/advance-practice/channels.md b/src/advance-practice/channels.md index 825d927d..fb0fa29e 100644 --- a/src/advance-practice/channels.md +++ b/src/advance-practice/channels.md @@ -225,7 +225,7 @@ let (tx, rx) = oneshot::channel(); 使用方式跟 `mpsc` 很像,但是它并没有缓存长度,因为只能发送一条,接收一条,还有一点不同:你无法对返回的两个句柄进行 `clone`。 -为了让管理任务将结果准确的返回到发送者手中,这个管道的发送端必须要随着命令一起发送, 然后发出命令的任务保留管道的发送端。一个比较好的实现就是将管道的发送端放入 `Command` 的数据结构中,同时使用一个别名来代表该发送端: +为了让管理任务将结果准确的返回到发送者手中,这个管道的发送端必须要随着命令一起发送, 然后发出命令的任务保留管道的接收端。一个比较好的实现就是将管道的发送端放入 `Command` 的数据结构中,同时使用一个别名来代表该发送端: ```rust use tokio::sync::oneshot; diff --git a/src/advance-practice1/multi-threads.md b/src/advance-practice1/multi-threads.md index ae5ff53d..7e2871f1 100644 --- a/src/advance-practice1/multi-threads.md +++ b/src/advance-practice1/multi-threads.md @@ -527,7 +527,7 @@ impl Worker { 一旦获取到锁里的内容 `mpsc::Receiver>` 后,就可以调用其上的 `recv` 方法来接收消息,依然是一个 `unwrap`,原因在于持有发送端的线程可能会被关闭,这种情况下直接 `panic` 也是不错的。 -`recv` 的调用过程是阻塞的,意味着若没有任何任务,那当前的调用线程将一直等待,直到接收到新的任务。`Mutex` 可以同一个任务只会被一个 Worker 获取,不会被重复执行。 +`recv` 的调用过程是阻塞的,意味着若没有任何任务,那当前的调用线程将一直等待,直到接收到新的任务。`Mutex` 可以保证同一个任务只会被一个 Worker 获取,不会被重复执行。 ```shell $ cargo run @@ -570,7 +570,7 @@ Worker 2 got a job; executing. 终于,程序如愿运行起来,我们的线程池可以并发处理任务了!从打印的数字可以看到,只有 4 个线程去执行任务,符合我们对线程池的要求,这样再也不用担心系统的线程资源会被消耗殆尽了! -> 注意: 处于缓存的考虑,有些浏览器会对多次同样的请求进行顺序的执行,因此你可能还是会遇到访问 `/sleep` 后,就无法访问另一个 `/sleep` 的问题 :( +> 注意: 出于缓存的考虑,有些浏览器会对多次同样的请求进行顺序的执行,因此你可能还是会遇到访问 `/sleep` 后,就无法访问另一个 `/sleep` 的问题 :( ## while let 的巨大陷阱 diff --git a/src/advance/async/pin-unpin.md b/src/advance/async/pin-unpin.md index ba227cad..12f2ae28 100644 --- a/src/advance/async/pin-unpin.md +++ b/src/advance/async/pin-unpin.md @@ -16,9 +16,9 @@ struct SelfRef { } ``` -在上面的结构体中,`pointer_to_value` 是一个裸指针,指向第一个字段 `value` 持有的字符串 `String` 。很简单对吧?现在考虑一个情况, 若`String` 被移动了怎么办? +在上面的结构体中,`pointer_to_value` 是一个裸指针,指向第一个字段 `value` 持有的字符串 `String` 。很简单对吧?现在考虑一个情况, 若 `value` 被移动了怎么办? -此时一个致命的问题就出现了:新的字符串的内存地址变了,而 `pointer_to_value` 依然指向之前的地址,一个重大 bug 就出现了! +此时一个致命的问题就出现了:`value` 的内存地址变了,而 `pointer_to_value` 依然指向 `value` 之前的地址,一个重大 bug 就出现了! 灾难发生,英雄在哪?只见 `Pin` 闪亮登场,它可以防止一个类型在内存中被移动。再来回忆下之前在 `Future` 章节中,我们提到过在 `poll` 方法的签名中有一个 `self: Pin<&mut Self>` ,那么为何要在这里使用 `Pin` 呢? diff --git a/src/advance/functional-programing/closure.md b/src/advance/functional-programing/closure.md index bc9b14bd..6775de72 100644 --- a/src/advance/functional-programing/closure.md +++ b/src/advance/functional-programing/closure.md @@ -591,7 +591,7 @@ fn exec<'a, F: Fn(String) -> ()>(f: F) { #### move 和 Fn -在上面,我们讲到了 `move` 关键字对于 `FnOnce` 特征的重要性,但是实际上使用了 `move` 的闭包依然可能实现了 `Fn` 或 `FnMut` 特征。 +在上面,我们讲到了 `move` 关键字对于 `FnOnce` 特征的重要性,但是实际上使用了 `move` 的闭包依然可以使用 `Fn` 或 `FnMut` 特征。 因为,**一个闭包实现了哪种 Fn 特征取决于该闭包如何使用被捕获的变量,而不是取决于闭包如何捕获它们**。`move` 本身强调的就是后者,闭包如何捕获变量: @@ -609,9 +609,9 @@ fn exec(f: F) { } ``` -我们在上面的闭包中使用了 `move` 关键字,所以我们的闭包捕获了它,但是由于闭包对 `s` 的使用仅仅是不可变借用,因此该闭包实际上**还**实现了 `Fn` 特征。 +我们在上面的闭包中使用了 `move` 关键字,所以我们的闭包捕获了它,但是由于闭包获取了 `s` 的所有权,因此该闭包实现了 `FnOnce` 的特征。 -细心的读者肯定发现我在上段中使用了一个 `还` 字,这是什么意思呢?因为该闭包不仅仅实现了 `FnOnce` 特征,还实现了 `Fn` 特征,将代码修改成下面这样,依然可以编译: +但是假如我们将代码修改成下面这样,依然可以编译: ```rust fn main() { @@ -627,6 +627,8 @@ fn exec(f: F) { } ``` +奇怪, 明明是闭包实现的是 `FnOnce` 的特征, 为什么编译器居然允许 `Fn` 特征通过编译呢? + #### 三种 Fn 的关系 实际上,一个闭包并不仅仅实现某一种 `Fn` 特征,规则如下: diff --git a/src/advance/lifetime/advance.md b/src/advance/lifetime/advance.md index 66a544f7..2bb91a3b 100644 --- a/src/advance/lifetime/advance.md +++ b/src/advance/lifetime/advance.md @@ -422,7 +422,7 @@ impl Reader for BufReader<'_> { } ``` -`'_` 生命周期表示 `BufReader` 有一个不使用的生命周期,我们可以忽略它,无需为它创建一个名称。 +`'_` 称为匿名生命周期(anonymous lifetime),在这里表示 `BufReader` 有一个不使用的生命周期,我们可以忽略它,无需为它创建一个名称。 歪个楼,有读者估计会发问:既然用不到 `'a`,为何还要写出来?如果你仔细回忆下上一节的内容,里面有一句专门用粗体标注的文字:**生命周期参数也是类型的一部分**,因此 `BufReader<'a>` 是一个完整的类型,在实现它的时候,你不能把 `'a` 给丢了! diff --git a/src/advance/smart-pointer/drop.md b/src/advance/smart-pointer/drop.md index a80a9ac6..7ab7bb28 100644 --- a/src/advance/smart-pointer/drop.md +++ b/src/advance/smart-pointer/drop.md @@ -158,6 +158,8 @@ Bingo,完美拿走了所有权,而且这种实现保证了后续的使用必 细心的同学可能已经注意到,这里直接调用了 `drop` 函数,并没有引入任何模块信息,原因是该函数在[`std::prelude`](https://course.rs/appendix/prelude.html)里。 +> 事实上,能被显式调用的drop(_x)函数只是个空函数,在拿走目标值的所有权后没有任何操作。而由于其持有目标值的所有权,在drop(_x)函数结束之际,编译器会执行_x真正的析构函数,从而完成释放资源的操作。换句话说,drop(_x)函数只是帮助目标值的所有者提前离开了作用域。https://doc.rust-lang.org/std/mem/fn.drop.html + ## Drop 使用场景 对于 Drop 而言,主要有两个功能: @@ -171,7 +173,7 @@ Bingo,完美拿走了所有权,而且这种实现保证了后续的使用必 ## 互斥的 Copy 和 Drop -我们无法为一个类型同时实现 `Copy` 和 `Drop` 特征。因为实现了 `Copy` 的特征会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了 `Copy` 的类型无法拥有析构函数。 +我们无法为一个类型同时实现 `Copy` 和 `Drop` 特征。因为实现了 `Copy` 特征的类型会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了 `Copy` 的类型无法拥有析构函数。 ```rust #[derive(Copy)] diff --git a/src/appendix/rust-versions/1.63.md b/src/appendix/rust-versions/1.63.md index c3478f7a..5f7d7438 100644 --- a/src/appendix/rust-versions/1.63.md +++ b/src/appendix/rust-versions/1.63.md @@ -1,4 +1,4 @@ -# Rust 新版解读 | 1.63 | 重点: Socped threads +# Rust 新版解读 | 1.63 | 重点: Scoped threads > Rust 1.63 官方 release doc: [Announcing Rust 1.63.0 | Rust Blog](https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html) diff --git a/src/appendix/rust-versions/1.78.md b/src/appendix/rust-versions/1.78.md new file mode 100644 index 00000000..fa263990 --- /dev/null +++ b/src/appendix/rust-versions/1.78.md @@ -0,0 +1,114 @@ +# Rust 新版解读 | 1.78 | 诊断属性宏 + +> Rust 1.78 官方 release doc: [Announcing Rust 1.78.0 | Rust Blog](https://blog.rust-lang.org/2024/05/02/Rust-1.78.0.html) + +通过 [rustup](https://www.rust-lang.org/tools/install) 安装的同学可以使用以下命令升级到 1.78 版本: + +```shell +$ rustup update stable +``` + +## 诊断属性宏 + +Rust 现在支持 `#[diagnostic]` 属性命名空间,用于影响编译器错误消息。这些被视为提示,编译器不需要使用它们,也不会因为提供了编译器不认识的诊断而报错。这种灵活性允许源代码提供诊断,即使不是所有编译器都支持,无论是不同版本还是完全不同的实现。 + +随着这个命名空间的出现,第一个支持的属性 `#[diagnostic::on_unimplemented]` 也随之而来,可以放在一个 trait 上,用于自定义当需要但未在类型上实现该 trait 时的消息。考虑下面来自[稳定 PR](https://github.com/rust-lang/rust/pull/119888/) 里的示例: + +```rust +#[diagnostic::on_unimplemented( + message = "My Message for `ImportantTrait<{A}>` is not implemented for `{Self}`", + label = "My Label", + note = "Note 1", + note = "Note 2" +)] +trait ImportantTrait {} + +fn use_my_trait(_: impl ImportantTrait) {} + +fn main() { + use_my_trait(String::new()); +} +``` + +此前,编译器会给出一个内置错误,如下: + +```shell +error[E0277]: the trait bound `String: ImportantTrait` is not satisfied + --> src/main.rs:12:18 + | +12 | use_my_trait(String::new()); + | ------------ ^^^^^^^^^^^^^ the trait `ImportantTrait` is not implemented for `String` + | | + | required by a bound introduced by this call + | +``` + +现在,使用 `#[diagnostic::on_unimplemented]`,自定义消息填充主要错误行,自定义标签放在源输出上。原始标签仍然写在帮助输出中,任何自定义注释也会被写入。 (这些细节未来可能会发生变化。) + +```shell +error[E0277]: My Message for `ImportantTrait` is not implemented for `String` + --> src/main.rs:12:18 + | +12 | use_my_trait(String::new()); + | ------------ ^^^^^^^^^^^^^ My Label + | | + | required by a bound introduced by this call + | + = help: the trait `ImportantTrait` is not implemented for `String` + = note: Note 1 + = note: Note 2 +``` + +对于 trait 作者来说,如果你能提供更好的提示,而不是仅仅给出缺失部分,这种诊断就更有用。例如,这是标准库中的一个示例: + +```rust +#[diagnostic::on_unimplemented( + message = "the size for values of type `{Self}` cannot be known at compilation time", + label = "doesn't have a size known at compile-time" +)] +pub trait Sized {} +``` + +更多信息,请参考[诊断工具属性命名空间](https://doc.rust-lang.org/stable/reference/attributes/diagnostics.html#the-diagnostic-tool-attribute-namespace)的参考部分。 + +## 不安全前提断言 + +Rust 标准库有许多用于不安全函数前提的断言,但历史上它们只在标准库的 `#[cfg(debug_assertions)]` 构建中启用,以避免影响发布性能。然而,由于标准库通常以发布模式编译和分发,大多数 Rust 开发者根本没有执行这些检查。 + +现在,这些断言的条件被延迟到代码生成,因此它们将根据用户自己对调试断言的设置进行检查(在调试和测试构建中默认启用)。这个变化有助于用户捕获他们代码中的未定义行为。 + +例如,`slice::from_raw_parts` 需要一个对齐的非空指针。下面故意错位指针的使用有未定义行为,虽然如果你运气不好,它可能在过去看起来“工作”,但调试断言现在可以捕获它: + +```rust +fn main() { + let slice: &[u8] = &[1, 2, 3, 4, 5]; + let ptr = slice.as_ptr(); + + // 创建一个 `ptr` 的偏移量,它总是比 `u16` 的正确对齐少一个 + let i = usize::from(ptr as usize & 1 == 0); + + let slice16: &[u16] = unsafe { std::slice::from_raw_parts(ptr.add(i).cast::(), 2) }; + dbg!(slice16); +} +``` + +在调试构建中,这将导致一个 panic: + +```shell +thread 'main' panicked at library/core/src/panicking.rs:220:5: +unsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX` +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +thread caused non-unwinding panic. aborting. +``` + +## 确定性重新对齐 + +标准库有一些函数可以改变指针和切片的对齐方式,但以前它们有一些注意事项,使它们在实践中难以依赖,如果你严格遵循它们的文档。这些注意事项主要是为了保护对 `const` 的判断,但它们只对非 `const` 使用稳定。现在它们承诺根据实际输入具有一致的运行时行为。 + +* [`pointer::align_offset`](https://doc.rust-lang.org/std/primitive.pointer.html#method.align_offset) 计算改变指针到给定对齐方式所需的偏移量。如果不可能,它将返回 `usize::MAX`,但以前允许它始终返回 `usize::MAX`,现在这种行为被移除。 + +* [`slice::align_to`](https://doc.rust-lang.org/std/primitive.slice.html#method.align_to) 和 [`slice::align_to_mut`](https://doc.rust-lang.org/std/primitive.slice.html#method.align_to_mut) 都将切片转换为对齐的中间切片和剩余的不对齐头和尾切片。这些方法现在承诺返回最大可能的中间部分,而不允许实现返回不那么优化的东西,比如将所有东西作为头切片返回。 + +## Others + +其它更新细节,和稳定的API列表,参考[原Blog](https://blog.rust-lang.org/2024/05/02/Rust-1.78.0.html#stabilized-apis) diff --git a/src/appendix/rust-versions/1.79.md b/src/appendix/rust-versions/1.79.md new file mode 100644 index 00000000..cae390a1 --- /dev/null +++ b/src/appendix/rust-versions/1.79.md @@ -0,0 +1,82 @@ +# Rust 新版解读 | 1.79 | 内联 const,临时变量生命周期延长 + +> Rust 1.79 官方 release doc: [Announcing Rust 1.79.0 | Rust Blog](https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html) + +通过 [rustup](https://www.rust-lang.org/tools/install) 安装的同学可以使用以下命令升级到 1.79 版本: + +```shell +$ rustup update stable +``` + +## 内联 `const` 表达式 + +如今可以写内联 const 块 `const {...}` 作为表达式,显式地进入 const 上下文,而不需要额外的声明(例如,定义 `const` 常量或 Trait 的关联常量)。 + +与 const 常量 `const ITEM: ... = ...` 不同,内联 const 里类型可以被推断而不需要显式写出,并且还能使用泛型参数。来看一个很实用的例子: + +```rust +const EMPTY: Option> = None; +let foo = [EMPTY; 100]; +``` + +如今可以写成如下形式,(foo 的类型 `Option` 可以不标注,可以根据上下文推断出来) + +```rust +let foo = [const { None }; 100]; +``` + +泛型的例子: + +```rust +fn create_none_array() -> [Option; N] { + [const { None }; N] +} +``` + +更多细节见[参考文档](https://doc.rust-lang.org/nightly/reference/expressions/block-expr.html#const-blocks) + +## 关联类型约束 + +Rust 1.79 稳定了一些关联类型约束的语法,允许我们在类型约束里写其它类型约束,即 `T: Trait`。这避免了提供额外的显式泛型类型来约束关联类型。 + +这个新特性允许我们在一些情况下更简单地指定好约束关系,解决了一些之前不可能或者会引入额外不必要约束的场景。 + +- **`where` 子句** - 在这个位置,这等同于将约束拆分为两个(或更多)`where` 语句。例如,`where T: Trait` 等同于 `where T: Trait, ::Assoc: Bound`。 +- **Supertraits** - 类似于上面,`trait CopyIterator: Iterator {}`。这也等同于将约束拆分为两个(或更多)`where` 语句;不过当 trait 被使用时,这个对关联类型 Item 的约束是隐含的。 +- **关联类型 Item 约束** - 允许约束与 trait 的关联类型相关的嵌套类型约束。例如 `trait Trait { type Assoc: Trait2; }`。 +- **模糊类型约束**(RPIT: return position `impl Trait`, TAIT: type alias `impl Trait`) - 允许约束与模糊类型相关的关联类型。例如 `impl Iterator` 定义了 Item 满足 Copy 的迭代器,而不必实际命名该约束。 + +更多细节见 [issue](https://github.com/rust-lang/rust/pull/122055/#issue-2170532454) + +译注:很绕,但是整体上就是一次让 Rust 编译器变得更符合你期望它应该正常工作的样子的更新。 + +## 临时变量生命周期延长 + +现在,在 `match` 和 `if` 结构中构造并立刻被使用的临时变量的生命周期会自动延长。这与代码结构中的临时变量生命周期延长的效果一致。 + +```rust +let a = if true { + ..; + &temp() // used to error, but now gets lifetime extended +} else { + ..; + &temp() // used to error, but now gets lifetime extended +}; + +let a = match () { + _ => { + ..; + &temp() // used to error, but now gets lifetime extended + } +}; + +// 之前已有的代码块临时变量生命周期延长 +let a = { + ..; + &temp() // lifetime is extended +}; +``` + +## Others + +其它更新细节,和稳定的 API 列表,参考[原Blog](https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html#stabilized-apis) diff --git a/src/appendix/rust-versions/1.80.md b/src/appendix/rust-versions/1.80.md new file mode 100644 index 00000000..b41cf913 --- /dev/null +++ b/src/appendix/rust-versions/1.80.md @@ -0,0 +1,104 @@ +# Rust 新版解读 | 1.80 | “懒”类型、开区间范围模式 + +> Rust 1.80 官方 release doc: [Announcing Rust 1.80.0 | Rust Blog](https://blog.rust-lang.org/2024/07/25/Rust-1.80.0.html) + +通过 [rustup](https://www.rust-lang.org/tools/install) 安装的同学可以使用以下命令升级到 1.80 版本: + +```shell +$ rustup update stable +``` + +## `LazyCell` 和 `LazyLock` + +这两个 "lazy" 类型会延迟数据的初始化直到第一次访问。它们类似于 1.70 版本中稳定的 `OnceCell` 和 `OnceLock` 类型,但是初始化函数包含在 cell 中。这完成了吸纳流行的 [`lazy_static`](https://crates.io/crates/lazy-static) 和 [`once_cell`](https://crates.io/crates/once_cell) crates 中的功能到标准库中。 + +`LazyLock` 是线程安全的选项,适用于静态值的场景。例如,下面的例子中,无论是 spawn 线程还是主线程,都会看到相同的时间,因为 `LAZY_TIME` 只会被初始化一次,由哪个访问静态值的端点先访问。与 `OnceLock::get_or_init()` 不同,使用方都不需要知道如何初始化它。 + +```rust +use std::sync::LazyLock; +use std::time::Instant; + +static LAZY_TIME: LazyLock = LazyLock::new(Instant::now); + +fn main() { + let start = Instant::now(); + std::thread::scope(|s| { + s.spawn(|| { + println!("Thread lazy time is {:?}", LAZY_TIME.duration_since(start)); + }); + println!("Main lazy time is {:?}", LAZY_TIME.duration_since(start)); + }); +} +``` + +`LazyCell` 也是一样的,只是没有线程同步,所以它不实现 `Sync`,但是它仍然可以在 `thread_local!` 静态值中使用(每个线程初始化一次)。取决于线程安全的需求如何,这两种类型都可以在其他数据结构中使用,来达到懒初始化的目的。 + +## `cfg` 名称和值检查 + +在 1.79 版本中,rustc 稳定了一个 [`--check-cfg` 标志](https://doc.rust-lang.org/rustc/check-cfg.html),现在 Cargo 1.80 为所有的 `cfg` 名称和值启用了这些检查(除了 rustc 中[已知的名称和值](https://doc.rust-lang.org/rustc/check-cfg.html#well-known-names-and-values))。这包括来自 Cargo.toml 的特性名称以及来自构建脚本的新 `cargo::rustc-check-cfg` 输出。 + +出现期望外的 cfgs 会默认生成 `unexpected_cfgs` lint,用于捕获拼写错误或其他配置错误。例如,在一个有可选的 `rayon` 特性依赖项的项目中,这段代码配置了错误的特性值: + +```rust +fn main() { + println!("Hello, world!"); + + #[cfg(feature = "crayon")] + rayon::join( + || println!("Hello, Thing One!"), + || println!("Hello, Thing Two!"), + ); +} +``` + +```bash +warning: unexpected `cfg` condition value: `crayon` + --> src/main.rs:4:11 + | +4 | #[cfg(feature = "crayon")] + | ^^^^^^^^^^-------- + | | + | help: there is a expected value with a similar name: `"rayon"` + | + = note: expected values for `feature` are: `rayon` + = help: consider adding `crayon` as a feature in `Cargo.toml` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default +``` + +无论实际是否启用 rayon 特性,都会报告该警告。 + +`Cargo.toml` 文件中的 `[lints]` 部分可以用于扩展自定义 `cfg` 的已知名称和值列表。`rustc` 自动提供了警告中使用的[语法](https://doc.rust-lang.org/rustc/check-cfg.html#specifying-expected-names-and-values)。 + +```toml +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(foo, values("bar"))'] } +``` + +你可以在之前的发布 nightly 时的[博客文章](https://blog.rust-lang.org/2024/05/06/check-cfg.html)中阅读更多关于这个特性的内容。 + +## 开区间的范围模式 + +Rust 的范围模式现在可以使用右开区间,写作 `a..b` 或 `..b`,类似于 `Range` 和 `RangeTo` 表达式类型。例如,以下模式现在可以使用相同的常量作为一个模式的结束和下一个模式的开始: + +```rust +pub fn size_prefix(n: u32) -> &'static str { + const K: u32 = 10u32.pow(3); + const M: u32 = 10u32.pow(6); + const G: u32 = 10u32.pow(9); + match n { + ..K => "", + K..M => "k", + M..G => "M", + G.. => "G", + } +} +``` + +之前,范围模式中只允许包含的(`a..=b` 或 `..=b`)或开放的(`a..`)范围,所以像这样的代码需要为边界使用一个不同的常量(如 `K - 1`)。 + +该特性作为一个不稳定的特性已经实现了很长时间,阻碍其稳定的考虑主要是它可能会增加模式匹配中的混乱程度,增加出现差一错误的机会。为此,穷尽性检查已经得到了增强,以更好地检测模式匹配中的空缺,新的 lint `non_contiguous_range_endpoints` 和 `overlapping_range_endpoints` 将有助于检测是否正确使用了开区间和闭区间。 + +## Others + +其它更新细节,和稳定的 API 列表,参考[原Blog](https://blog.rust-lang.org/2024/07/25/Rust-1.80.0.html#stabilized-apis) diff --git a/src/basic/base-type/numbers.md b/src/basic/base-type/numbers.md index 25d47350..cd785ca8 100644 --- a/src/basic/base-type/numbers.md +++ b/src/basic/base-type/numbers.md @@ -258,27 +258,33 @@ Rust 的位运算基本上和其他语言一样 ```rust fn main() { - // 二进制为00000010 - let a:i32 = 2; + // 无符号8位整数,二进制为00000010 + let a: u8 = 2; // 也可以写 let a: u8 = 0b_0000_0010; + // 二进制为00000011 - let b:i32 = 3; + let b: u8 = 3; + + // {:08b}:左高右低输出二进制01,不足8位则高位补0 + println!("a value is {:08b}", a); + + println!("b value is {:08b}", b); - println!("(a & b) value is {}", a & b); + println!("(a & b) value is {:08b}", a & b); - println!("(a | b) value is {}", a | b); + println!("(a | b) value is {:08b}", a | b); - println!("(a ^ b) value is {}", a ^ b); + println!("(a ^ b) value is {:08b}", a ^ b); - println!("(!b) value is {} ", !b); + println!("(!b) value is {:08b}", !b); - println!("(a << b) value is {}", a << b); + println!("(a << b) value is {:08b}", a << b); - println!("(a >> b) value is {}", a >> b); + println!("(a >> b) value is {:08b}", a >> b); let mut a = a; // 注意这些计算符除了!之外都可以加上=进行赋值 (因为!=要用来判断不等于) a <<= b; - println!("(a << b) value is {}", a); + println!("(a << b) value is {:08b}", a); } ``` diff --git a/src/basic/compound-type/string-slice.md b/src/basic/compound-type/string-slice.md index fd34eef7..28ae5fb8 100644 --- a/src/basic/compound-type/string-slice.md +++ b/src/basic/compound-type/string-slice.md @@ -49,7 +49,7 @@ let world = &s[6..11]; `hello` 没有引用整个 `String s`,而是引用了 `s` 的一部分内容,通过 `[0..5]` 的方式来指定。 -这就是创建切片的语法,使用方括号包括的一个序列:**[开始索引..终止索引]**,其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 `右半开区间`。在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 `终止索引` - `开始索引` 的方式计算得来的。 +这就是创建切片的语法,使用方括号包括的一个序列:**[开始索引..终止索引]**,其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置。换句话说,这是一个 `右半开区间`(或称为左闭右开区间)——指的是在区间的左端点是包含在内的,而右端点是不包含在内的。在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 `终止索引` - `开始索引` 的方式计算得来的。 对于 `let world = &s[6..11];` 来说,`world` 是一个切片,该切片的指针指向 `s` 的第 7 个字节(索引从 0 开始, 6 是第 7 个字节),且该切片的长度是 `5` 个字节。 @@ -399,6 +399,7 @@ string_replace_range = "I like Rust!" 1、 `pop` —— 删除并返回字符串的最后一个字符 **该方法是直接操作原来的字符串**。但是存在返回值,其返回值是一个 `Option` 类型,如果字符串为空,则返回 `None`。 + 示例代码如下: ```rust @@ -620,7 +621,7 @@ fn main() { let quotes = r#"And then I said: "There is no escape!""#; println!("{}", quotes); - // 如果还是有歧义,可以继续增加,没有限制 + // 如果字符串中包含 # 号,可以在开头和结尾加多个 # 号,最多加255个,只需保证与字符串中连续 # 号的个数不超过开头和结尾的 # 号的个数即可 let longer_delimiter = r###"A string with "# in it. And even "##!"###; println!("{}", longer_delimiter); } diff --git a/src/basic/crate-module/crate.md b/src/basic/crate-module/crate.md index 8cdf435c..5da55730 100644 --- a/src/basic/crate-module/crate.md +++ b/src/basic/crate-module/crate.md @@ -72,7 +72,7 @@ error: a bin target must be available for `cargo run` 看完上面,相信大家看出来为何 `Package` 和包容易被混淆了吧?因为你用 `cargo new` 创建的 `Package` 和它其中包含的包是同名的! -不过,只要你牢记 `Package` 是一个项目工程,而包只是一个编译单元,基本上也就不会混淆这个两个概念了:`src/main.rs` 和 `src/lib.rs` 都是编译单元,因此它们都是包。 +不过,只要你牢记 `Package` 是一个项目工程,而包只是一个编译单元,也就不会再混淆这两个概念:`src/main.rs` 和 `src/lib.rs` 都是编译单元,因此它们都是包。 #### 典型的 `Package` 结构 diff --git a/src/basic/crate-module/module.md b/src/basic/crate-module/module.md index 54d39915..0c44ad83 100644 --- a/src/basic/crate-module/module.md +++ b/src/basic/crate-module/module.md @@ -351,7 +351,16 @@ error[E0583]: file not found for module `front_of_house` - 在 `front_of_house` 目录里创建一个 `mod.rs`,如果你使用的 `rustc` 版本 `1.30` 之前,这是唯一的方法。 - 在 `front_of_house` **同级**目录里创建一个与模块(目录)**同名**的 rs 文件 `front_of_house.rs`,在新版本里,更建议使用这样的命名方式来避免项目中存在大量同名的 `mod.rs` 文件( Python 点了个 `踩`)。 -而无论是上述哪个方式创建的文件,其内容都是一样的,你需要定义你的子模块(子模块名与文件名相同): +如果使用第二种方式,文件结构将如下所示: +```shell +src +├── front_of_house +│ └── hosting.rs +├── front_of_house.rs +└── lib.rs +``` + +而无论是上述哪个方式创建的文件,其内容都是一样的,你需要在定义你(`mod.rs` 或 `front_of_house.rs`)的子模块(子模块名与文件名相同): ```rust pub mod hosting; diff --git a/src/basic/match-pattern/option.md b/src/basic/match-pattern/option.md index 12714b98..ed13a7a2 100644 --- a/src/basic/match-pattern/option.md +++ b/src/basic/match-pattern/option.md @@ -4,8 +4,8 @@ ```rust enum Option { - Some(T), None, + Some(T), } ``` diff --git a/src/basic/ownership/ownership.md b/src/basic/ownership/ownership.md index c5458bf6..6a98bc15 100644 --- a/src/basic/ownership/ownership.md +++ b/src/basic/ownership/ownership.md @@ -101,7 +101,7 @@ let s = "hello"; 之前提到过,本章会用 `String` 作为例子,因此这里会进行一下简单的介绍,具体的 `String` 学习请参见 [String 类型](https://course.rs/basic/compound-type/string-slice.html)。 -我们已经见过字符串字面值 `let s ="hello"`,`s` 是被硬编码进程序里的字符串值(类型为 `&str` )。字符串字面值是很方便的,但是它并不适用于所有场景。原因有二: +我们已经见过字符串字面值 `let s = "hello"`,`s` 是被硬编码进程序里的字符串值(类型为 `&str` )。字符串字面值是很方便的,但是它并不适用于所有场景。原因有二: - **字符串字面值是不可变的**,因为被硬编码到程序代码中 - 并非所有字符串的值都能在编写代码时得知 @@ -114,7 +114,7 @@ let s = "hello"; let s = String::from("hello"); ``` -`::` 是一种调用操作符,这里表示调用 `String` 模块中的 `from` 方法,由于 `String` 类型存储在堆上,因此它是动态的,你可以这样修改: +`::` 是一种调用操作符,这里表示调用 `String` 类型中的 `from` 关联函数,由于 `String` 类型存储在堆上,因此它是动态的,你可以这样修改: ```rust let mut s = String::from("hello"); diff --git a/src/basic/trait/advance-trait.md b/src/basic/trait/advance-trait.md index c9ef4863..16a574ed 100644 --- a/src/basic/trait/advance-trait.md +++ b/src/basic/trait/advance-trait.md @@ -310,7 +310,7 @@ fn main() { ## 特征定义中的特征约束 -有时,我们会需要让某个特征 A 能使用另一个特征 B 的功能(另一种形式的特征约束),这种情况下,不仅仅要为类型实现特征 A,还要为类型实现特征 B 才行,这就是 `supertrait` (实在不知道该如何翻译,有大佬指导下嘛?) +有时,我们会需要让某个特征 A 能使用另一个特征 B 的功能(另一种形式的特征约束),这种情况下,不仅仅要为类型实现特征 A,还要为类型实现特征 B 才行,这就是基特征( super trait )。 例如有一个特征 `OutlinePrint`,它有一个方法,能够对当前的实现类型进行格式化输出: diff --git a/src/basic/trait/generic.md b/src/basic/trait/generic.md index 2f3c529b..9f9bb5b0 100644 --- a/src/basic/trait/generic.md +++ b/src/basic/trait/generic.md @@ -419,7 +419,59 @@ impl IsTrue for Assert { #### const fn -@todo +在讨论完 `const` 泛型后,不得不提及另一个与之密切相关且强大的特性:`const fn`,即常量函数。`const fn` 允许我们在编译期对函数进行求值,从而实现更高效、更灵活的代码设计。 + +##### 为什么需要 const fn + +通常情况下,函数是在运行时被调用和执行的。然而,在某些场景下,我们希望在编译期就计算出一些值,以提高运行时的性能或满足某些编译期的约束条件。例如,定义数组的长度、计算常量值等。 + +有了 `const fn`,我们可以在编译期执行这些函数,从而将计算结果直接嵌入到生成的代码中。这不仅以高了运行时的性能,还使代码更加简洁和安全。 + +##### const fn 的基本用法 + +要定义一个常量函数,只需要在函数声明前加上 `const` 关键字。例如: + +```rust +const fn add(a: usize, b: usize) -> usize { + a + b +} + +const RESULT: usize = add(5, 10); + +fn main() { + println!("The result is: {}", RESULT); +} +``` + +##### const fn 的限制 + +虽然 `const fn` 提供了很多便利,但是由于其在编译期执行,以确保函数能在编译期被安全地求值,因此有一些限制,例如,不可将随机数生成器写成 `const fn`。 + +无论在编译时还是运行时调用 `const fn`,它们的结果总是相同,即使多次调用也是如此。唯一的例外是,如果你在极端情况下进行复杂的浮点操作,你可能会得到(非常轻微的)不同结果。因此,不建议使 `数组长度 (arr.len())` 和 `Enum判别式` 依赖于浮点计算。 + +##### 结合 const fn 与 const 泛型 + +将 `const fn` 与 `const 泛型` 结合,可以实现更加灵活和高效的代码设计。例如,创建一个固定大小的缓冲区结构,其中缓冲区大小由编译期计算确定: + +```rust +struct Buffer { + data: [u8; N], +} + +const fn compute_buffer_size(factor: usize) -> usize { + factor * 1024 +} + +fn main() { + const SIZE: usize = compute_buffer_size(4); + let buffer = Buffer:: { + data: [0; SIZE], + }; + println!("Buffer size: {} bytes", buffer.data.len()); +} +``` + +在这个例子中,`compute_buffer_size` 是一个常量函数,它根据传入的 `factor` 计算缓冲区的大小。在 `main` 函数中,我们使用 `compute_buffer_size(4)` 来计算缓冲区大小为 4096 字节,并将其作为泛型参数传递给 `Buffer` 结构体。这样,缓冲区的大小在编译期就被确定下来,避免了运行时的计算开销。 ## 泛型的性能 diff --git a/src/cargo/reference/build-script/examples.md b/src/cargo/reference/build-script/examples.md index 9dd7c1e2..8a6a0d70 100644 --- a/src/cargo/reference/build-script/examples.md +++ b/src/cargo/reference/build-script/examples.md @@ -4,7 +4,7 @@ - [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` 工具检测系统库 +- [pkg-config](https://crates.io/crates/pkg-config), 使用 `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` 的当前版本来实现条件编译的方法 diff --git a/src/cargo/reference/features/intro.md b/src/cargo/reference/features/intro.md index 4e6b0f9e..fadd9e83 100644 --- a/src/cargo/reference/features/intro.md +++ b/src/cargo/reference/features/intro.md @@ -70,7 +70,7 @@ webp = [] gif = { version = "0.11.1", optional = true } ``` -**这种可选依赖的写法会自动定义一个与依赖同名的 feature,也就是 `gif` feature**,这样一来,当我们启用 `gif` feature 时,该依赖库也会被自动引入并启用:例如通过 `--feature gif` 的方式启用 feature 。 +**这种可选依赖的写法会自动定义一个与依赖同名的 feature,也就是 `gif` feature**,这样一来,当我们启用 `gif` feature 时,该依赖库也会被自动引入并启用:例如通过 `--features gif` 的方式启用 feature 。 > 注意:目前来说,`[feature]` 中定义的 feature 还不能与已引入的依赖库同名。但是在 `nightly` 中已经提供了实验性的功能用于改变这一点: [namespaced features](https://doc.rust-lang.org/stable/cargo/reference/unstable.html#namespaced-features) diff --git a/src/compiler/fight-with-compiler/lifetime/loop.md b/src/compiler/fight-with-compiler/lifetime/loop.md index bedfcd8c..3184f1c5 100644 --- a/src/compiler/fight-with-compiler/lifetime/loop.md +++ b/src/compiler/fight-with-compiler/lifetime/loop.md @@ -292,9 +292,9 @@ fn get_mut(&mut self, name: &String) -> &mut Symbol { 其中的关键就在于返回的时候,新建一个引用,而不是使用中间状态的引用。 -## 新编译器 Polonius +## 新借用检查器 Polonius -针对现有编译器存在的各种问题,Rust 团队正在研发一个全新的编译器,名曰[`polonius`](https://github.com/rust-lang/polonius),但是目前它仍然处在开发阶段,如果想在自己项目中使用,需要在`rustc/RUSTFLAGS`中增加标志`-Zpolonius`,但是可能会导致编译速度变慢,或者引入一些新的编译错误。 +针对现有借用检查器存在的各种问题,Rust 团队正在研发一个全新的借用检查器,名曰[`polonius`](https://github.com/rust-lang/polonius),但是目前它仍然处在开发阶段,如果想在自己项目中使用,需要在`rustc/RUSTFLAGS`中增加标志`-Zpolonius`,但是可能会导致编译速度变慢,或者引入一些新的编译错误。 ## 总结 diff --git a/src/into-rust.md b/src/into-rust.md index 603152fb..6b4d4372 100644 --- a/src/into-rust.md +++ b/src/into-rust.md @@ -167,7 +167,7 @@ Rust 与 C++ 的性能旗鼓相当,但是在安全性方面 Rust 会更优, ## 总结 -连续 6 年最受欢迎的语言当然不是浪得虚名。 无 GC、效率高、工程性强、强安全性以及能同时得到工程派和学院派认可,这些令 Rust 拥有了自己的特色和生存空间。社区的友善,生态的快速发展,大公司的重仓跟进,一切的一切都在说明 Rust 的璀璨未来。 +连续 8 年最受欢迎的语言当然不是浪得虚名。 无 GC、效率高、工程性强、强安全性以及能同时得到工程派和学院派认可,这些令 Rust 拥有了自己的特色和生存空间。社区的友善,生态的快速发展,大公司的重仓跟进,一切的一切都在说明 Rust 的璀璨未来。 当然,语言毕竟只是工具,我们不能神话它,但是可以给它一个机会,也许,你最终能收获自己的真爱 :) diff --git a/src/profiling/performance/deep-into-move.md b/src/profiling/performance/deep-into-move.md index c1fe9e82..b1394081 100644 --- a/src/profiling/performance/deep-into-move.md +++ b/src/profiling/performance/deep-into-move.md @@ -208,7 +208,7 @@ fn main() { 完美符合了我们对堆上数据的预期,hooray! -#### 神龟莫测的编译器优化 +#### 神鬼莫测的编译器优化 当然,你也可以选择相信编译器的优化,虽然很难识它真面目,同时它的行为也神鬼莫测,但是总归是在之前的例子中证明了,它确实可以,不是嘛? = , =