Merge pull request #446 from 1132719438/main

Some fixes in cargo, fight-with-compiler and pitfalls chapter
pull/448/head
孙飞Sunface 3 years ago committed by GitHub
commit 9969ed10f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,5 @@
# 构建( Build )缓存
`cargo build` 的结果会被放入项目根目录下的 `target` 文件夹中,当然,这个位置可以三种方式更改:设置 `CARGO_TARGET_DIR` [环境变量](https://course.rs/cargo/reference/env.html)、[`build.target-dir`]((https://course.rs/cargo/reference/configuration.html#buildtarget-dir)) 配置项以及 `--target-dir` 命令行参数。
`cargo build` 的结果会被放入项目根目录下的 `target` 文件夹中,当然,这个位置可以三种方式更改:设置 `CARGO_TARGET_DIR` [环境变量](https://course.rs/cargo/reference/env.html)、[`build.target-dir`](https://course.rs/cargo/reference/configuration.html#buildtarget-dir) 配置项以及 `--target-dir` 命令行参数。
## target 目录结构
`target` 目录的结构取决于是否使用 `--target` 标志为特定的平台构建。

@ -42,11 +42,11 @@ $ echo $HOME/.cargo/
- `registry/cache/`
- `git/db/`
## 清缓存
## 清缓存
理论上,我们可以手动移除缓存中的任何一部分,当后续有包需要时 `Cargo` 会尽可能去恢复这些资源:
- 解压缩 `registry/src` 下的 `.crate` 档案
- 解压缩 `registry/cache` 下的 `.crate` 档案
- 从 `.git``checkout` 缓存的仓库
- 如果以上都没了,会从网络上重新下载
你也可以使用 [cargo-cache] 包来选择性的清除 `cache` 中指定的部分,当然,它还可以用来查看缓存中的组件大小。
你也可以使用 [cargo-cache](https://crates.io/crates/cargo-cache) 包来选择性的清除 `cache` 中指定的部分,当然,它还可以用来查看缓存中的组件大小。

@ -18,7 +18,7 @@
**那么问题来了,为何会有这种选择?**
原因是 `Cargo.lock`尽描述上一次成功构建的各种信息环境状态、依赖、版本等等Cargo 可以使用它提供确定性的构建环境和流程,无论何时何地。这种特性对于终端服务是非常重要的:能确定、稳定的在用户环境中运行起来是终端服务最重要的特性之一。
原因是 `Cargo.lock`尽描述上一次成功构建的各种信息环境状态、依赖、版本等等Cargo 可以使用它提供确定性的构建环境和流程,无论何时何地。这种特性对于终端服务是非常重要的:能确定、稳定的在用户环境中运行起来是终端服务最重要的特性之一。
而对于三方库来说,情况就有些不同。它不仅仅被库的开发者所使用,还会间接影响依赖链下游的使用者。用户引入了三方库是不会去看它的 `Cargo.lock` 信息的,也不应该受这个库的确定性运行条件所限制。
@ -35,7 +35,7 @@ version = "0.1.0"
regex = { git = "https://github.com/rust-lang/regex.git" }
```
可以看到,只有一个依赖,且该依赖的来源是 `Github` 上一个特定的仓库。由于我们没有指定任何版本信息,`Cargo` 会自动拉取该依赖库的最新版本( `master` 分支上的最新 `commit` )。
可以看到,只有一个依赖,且该依赖的来源是 `Github` 上一个特定的仓库。由于我们没有指定任何版本信息,`Cargo` 会自动拉取该依赖库的最新版本( `master` `main` 分支上的最新 `commit` )。
这种使用方式,其实就错失了包管理工具的最大的优点:版本管理。例如你在今天构建使用了版本 `A`,然后过了一段时间后,由于依赖包的升级,新的构建却使用了大更新版本 `B`,结果因为版本不兼容,导致了构建失败。

@ -1,7 +1,7 @@
# 智能指针引起的重复借用错误
本文将彻底解决一个困扰广大 Rust 用户已久的常见错误: 当智能指针和结构体一起使用时导致的借用错误: ` cannot borrow `mut_s` as mutable because it is also borrowed as immutable`.
相信看过[<<对抗Rust编译检查系列>>](https://www.zhihu.com/column/c_1461712984854335488)的读者都知道结构体中的不同字段可以独立借用吧?
相信看过[<<对抗Rust编译检查系列>>](https://course.rs/fight-with-compiler/intro.html)的读者都知道结构体中的不同字段可以独立借用吧?
## 结构体中的字段借用
不知道也没关系,我们这里再简单回顾一下:
@ -26,7 +26,7 @@ impl Test {
因此,虽然我们不能同时对整个结构体进行多次可变借用,但是我们可以分别对结构体中的不同字段进行可变借用,当然,一个字段至多也只能存在一个可变借用,这个最基本的所有权规则还是不能违反的。变量`a`引用结构体字段`a`,变量`b`引用结构体字段`b`,从底层来说,这种方式也不会造成两个可变引用指向了同一块内存。
## RefCell
如果你还不知道RefCell可以看看[这篇文章](https://zhuanlan.zhihu.com/p/453727091)当然不看也行简而言之RefCell能够实现:
如果你还不知道 RefCell可以看看[这篇文章](https://course.rs/advance/smart-pointer/cell-refcell.html)当然不看也行简而言之RefCell 能够实现:
- 将借用规则从编译期推迟到运行期,但是并不会饶过借用规则,当不符合时,程序直接`panic`
- 实现内部可变性:简单来说,对一个不可变的值进行可变借用,然后修改内部的值
@ -86,7 +86,7 @@ fn write(s: RefCell<S>) {
}
```
可以看出,对结构体字段的调用,实际上经过一层函数,一层函数!?我相信你应该想起了什么,是的,在[上一篇文章](https://zhuanlan.zhihu.com/p/451920390/edit)中讲过类似的问题, 大意就是**编译器对于函数往往只会分析签名,并不关心内部到底如何使用结构体**。
可以看出,对结构体字段的调用,实际上经过一层函数,一层函数!?我相信你应该想起了什么,是的,在[上一篇文章](https://course.rs/fight-with-compiler/borrowing/ref-exist-in-out-fn.html)中讲过类似的问题, 大意就是**编译器对于函数往往只会分析签名,并不关心内部到底如何使用结构体**。
而上面的`&Deref::deref(&mut_s)`和`DerefMut::deref_mut(&mut mut_s)`函数,签名全部使用的是结构体,并不是结构体中的某一个字段,因此对于编译器来说,该结构体明显是被重复借用了!

@ -2,7 +2,7 @@
本文将彻底解决一个困扰广大 Rust 用户已久的常见错误:因为在函数内外同时借用一个引用,导致了重复借用错误`cannot borrow *self as mutable because it is also borrowed as immutable`.
> 本文大部分内容节选自[Rust陷阱系列](https://www.zhihu.com/column/c_1454754106916806656)专题,由于借用是新手绕不过去的坎,因此将其提取出来形成一个新的系列
> 本文大部分内容节选自[Rust常见陷阱](https://course.rs/pitfalls/index.html)专题,由于借用是新手绕不过去的坎,因此将其提取出来形成一个新的系列
## 正确的代码
```rust
@ -109,7 +109,7 @@ fn increase(&mut self) {
## 解决办法
在深入分析中,我们提到一条重要的规则,要影响编译行为,就需要更改相关函数的签名,因此可以修改`increate_a`的签名:
在深入分析中,我们提到一条重要的规则,要影响编译行为,就需要更改相关函数的签名,因此可以修改`increase_a`的签名:
```rust
fn increase_a (a :&mut u32) {
*a += 1;

@ -2,7 +2,7 @@
特征对象是一个好东西闭包也是一个好东西但是如果两者你都想要时可能就会火星撞地球boom! 至于这两者为何会勾搭到一起?考虑一个常用场景:使用闭包作为回调函数.
## 学习目标
如何使用闭包作为特征对象,并解决以下错误:`the parameter type `impl Fn(&str) -> Res` may not live long enough`
如何使用闭包作为特征对象,并解决以下错误:`the parameter type ` \`impl Fn(&str) -> Res\` ` may not live long enough`
## 报错的代码
在下面代码中,我们通过闭包实现了一个简单的回调函数(错误代码已经标注)
@ -49,6 +49,16 @@ fn main() {
}
```
```
error[E0310]: the parameter type `impl Fn(&str) -> Res` may not live long enough
--> src/main.rs:25:30
|
24 | pub fn set(&mut self, cb: impl Fn(&str) -> Res) {
| -------------------- help: consider adding an explicit lifetime bound...: `impl Fn(&str) -> Res + 'static`
25 | self.callback = Some(Box::new(cb));
| ^^^^^^^^^^^^ ...so that the type `impl Fn(&str) -> Res` will meet its required lifetime bounds
```
从第一感觉来说,报错属实不应该,因为我们连引用都没有用,生命周期都不涉及,怎么就报错了?在继续深入之前,先来观察下该闭包是如何被使用的:
```rust
callback: Option<Box<dyn Fn(&str) -> Res>>,
@ -121,7 +131,7 @@ inl.set(move |val| {
// println!("{}", local);
```
如上所示,你也可以选择将本地变量的所有权`move`进闭包中,此时闭包再次具有`'statci`生命周期
如上所示,你也可以选择将本地变量的所有权`move`进闭包中,此时闭包再次具有`'static`生命周期
##### 4. 非要捕获本地变量的引用?
对于第2种情况如果非要这么干那`'static`肯定是没办法了,我们只能给予闭包一个新的生命周期:

@ -23,7 +23,7 @@ error: lifetime may not live long enough
咦?竟然报错了,明明两个一模一样功能的函数,一个正常编译,一个却报错,错误原因是编译器无法推测返回的引用和传入的引用谁活得更久!
真的是非常奇怪的错误,学过[Rust生命周期](https://github.com/sunface/rust-course/blob/main/src/advance/lifetime/basic.md)的读者应该都记得这样一条生命周期消除规则: **如果函数参数中只有一个引用类型,那该引用的生命周期会被自动分配给所有的返回引用**。我们当前的情况完美符合,`function`函数的顺利编译通过,就充分说明了问题。
真的是非常奇怪的错误,学过[Rust生命周期](https://course.rs/advance/lifetime/basic.html)的读者应该都记得这样一条生命周期消除规则: **如果函数参数中只有一个引用类型,那该引用的生命周期会被自动分配给所有的返回引用**。我们当前的情况完美符合,`fn_elision`函数的顺利编译通过,就充分说明了问题。
那为何闭包就出问题了?
@ -105,4 +105,4 @@ let closure_slision = |x: &i32| -> &i32 { x };
## 总结
虽然我言之凿凿,闭包的生命周期无法解决,但是未来谁又知道呢。最大的可能性就是之前开头那种简单的场景,可以被自动识别和消除。
总之,如果有这种需求,还是像古天乐一样做一个平平无奇的男人,老老实实使用函数吧.
总之,如果有这种需求,还是像古天乐一样做一个平平无奇的男人,老老实实使用函数吧

@ -1,3 +1,3 @@
# Rust陷阱系列
本章收录一些Rust常见的陷阱一不小心就会坑你的那种(当然这不是Rust语言的问题而是一些边边角角知识点)。
本章收录一些 Rust 常见的陷阱,一不小心就会坑你的那种(当然,这不是 Rust 语言的问题,而是一些边边角角知识点)。

@ -71,7 +71,7 @@ accounts.into_iter().map(|a| {
> 事实上IDE和编译器都会对这种代码给出警告iterators are lazy and do nothing unless consumed
## 解决办法
原因非常清晰,如果读者还有疑惑,建议深度下上面给出的迭代器链接,我们这里就不再赘述。
原因非常清晰,如果读者还有疑惑,建议深度了解下上面给出的迭代器链接,我们这里就不再赘述。
下面列出三种合理的解决办法:

@ -117,7 +117,7 @@ fn increase(&mut self) {
## 解决办法
在深入分析中,我们提到一条重要的规则,要影响编译行为,就需要更改相关函数的签名,因此可以修改`increate_a`的签名:
在深入分析中,我们提到一条重要的规则,要影响编译行为,就需要更改相关函数的签名,因此可以修改`increase_a`的签名:
```rust
fn increase_a (a :&mut u32) {
*a += 1;

@ -45,7 +45,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
```
Bingo, 成功了,最后再补充点测试的背景知识:
> cargo test为何使用新线程因为它需要并行的运行测试用例与之相反cargo bench只需要顺序的执行因此main线程足矣
> `cargo test`为何使用新线程?因为它需要并行的运行测试用例,与之相反,`cargo bench`只需要顺序的执行因此main线程足矣

@ -10,7 +10,7 @@ for i in 0..v.len() {
}
```
我们的目的是创建一个无限增长的数组,往里面插入`0..`(看不懂该表达式的同学请查阅https://course.rs)的数值序列。
我们的目的是创建一个无限增长的数组,往里面插入`0..`(看不懂该表达式的同学请查阅[流程控制](https://course.rs/basic/flow-control.html))的数值序列。
看起来上面代码可以完成,因为随着数组不停增长,`v.len()`也会不停变大,但是事实上真的如此吗?

Loading…
Cancel
Save