From 14b358cd450eba6b3e9b3df1ee8c5ea33f2dd566 Mon Sep 17 00:00:00 2001 From: sunface Date: Mon, 3 Jan 2022 21:59:50 +0800 Subject: [PATCH 1/8] add Drop --- course-book/contents/SUMMARY.md | 17 +- .../contents/advance/smart-pointer/deref.md | 2 +- .../contents/advance/smart-pointer/drop.md | 183 ++++++++++++++++++ 3 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 course-book/contents/advance/smart-pointer/drop.md diff --git a/course-book/contents/SUMMARY.md b/course-book/contents/SUMMARY.md index b0c18399..5ddd9156 100644 --- a/course-book/contents/SUMMARY.md +++ b/course-book/contents/SUMMARY.md @@ -67,16 +67,17 @@ - [智能指针 doing](advance/smart-pointer/intro.md) - [Box堆对象分配](advance/smart-pointer/box.md) - [Deref解引用](advance/smart-pointer/deref.md) - - [Cell todo](advance/smart-pointer/cell.md) - - [Rc与RefCell(todo)](advance/smart-pointer/rc-refcell.md) - - [自引用与内存泄漏(todo)](advance/smart-pointer/self-referrence.md) + - [Drop释放资源](advance/smart-pointer/drop.md) + - [Cell与RefCell todo](advance/smart-pointer/cell.md) + - [Rc与Arc todo](advance/smart-pointer/rc-refcell.md) + - [自引用与内存泄漏 todo](advance/smart-pointer/self-referrence.md) - [全局变量 todo](advance/global-variable.md) - [多线程 todo](advance/multi-threads/intro.md) - - [线程管理(todo)](advance/multi-threads/thread.md) - - [消息传递(todo)](advance/multi-threads/message-passing.md) - - [数据共享Arc、Mutex、Rwlock(todo)](advance/multi-threads/ref-counter-lock.md) - - [数据竞争(todo)](advance/multi-threads/races.md) - - [Send、Sync(todo)](advance/multi-threads/send-sync.md) + - [线程管理 todo](advance/multi-threads/thread.md) + - [消息传递 todo](advance/multi-threads/message-passing.md) + - [数据共享Mutex、Rwlock todo](advance/multi-threads/ref-counter-lock.md) + - [数据竞争 todo](advance/multi-threads/races.md) + - [Send、Sync todo](advance/multi-threads/send-sync.md) ## 专题内容,每个专题都配套一个小型项目进行实践 - [Rust最佳实践 doing](practice/intro.md) diff --git a/course-book/contents/advance/smart-pointer/deref.md b/course-book/contents/advance/smart-pointer/deref.md index 9ac9f9b8..fd842767 100644 --- a/course-book/contents/advance/smart-pointer/deref.md +++ b/course-book/contents/advance/smart-pointer/deref.md @@ -1,5 +1,5 @@ # Deref解引用 -智能指针的名称来源,主要就在于它实现了`Deref`和`Drop`特征,这两个特征可以智能地帮助我们节省使用上的负担: +何为智能指针?能不让你写出&&&&&&s形式的解引用,我认为就是智能: ) 智能指针的名称来源,主要就在于它实现了`Deref`和`Drop`特征,这两个特征可以智能地帮助我们节省使用上的负担: - `Deref`可以让智能指针像引用那样工作,这样你就就可以写出同时支持智能指针和引用的代码, 例如`&T` - `Drop`允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作 diff --git a/course-book/contents/advance/smart-pointer/drop.md b/course-book/contents/advance/smart-pointer/drop.md new file mode 100644 index 00000000..d731a460 --- /dev/null +++ b/course-book/contents/advance/smart-pointer/drop.md @@ -0,0 +1,183 @@ +# Drop释放资源 +在Rust中,我们之所以可以一拳打跑GC的同时一脚踢翻手动资源回收,主要就归功于`Drop`特征,同时它也是智能指针的必备特征之一。 + +## 学习目标 +如何自动和手动释放资源及执行指定的收尾工作 + +## Rust中的资源回收 +在一些无GC语言中,程序员在一个变量无需再被使用时,需要手动释放它占用的内存资源,如果忘记了,那么就会发生内存泄漏,最终臭名昭著的`OOM`问题可能就会发生。 + +而在Rust中,你可以指定在一个变量超出作用域时,执行一段特定的代码,最终编译器将帮你自动插入这段收尾代码。这样,就无需在每一个使用该变量的地方,都写一段代码来进行收尾工作和资源释放。不仅让人感叹,Rust的大腿真粗,香! + +没错,指定这样一段收尾工作靠的就是咱这章的主角 - `Drop`特征。 + +## 一个不那么简单的Drop例子 +```rust +struct HasDrop1; +struct HasDrop2; +impl Drop for HasDrop1 { + fn drop(&mut self) { + println!("Dropping HasDrop1!"); + } +} +impl Drop for HasDrop2 { + fn drop(&mut self) { + println!("Dropping HasDrop2!"); + } +} +struct HasTwoDrops { + one: HasDrop1, + two: HasDrop2, +} +impl Drop for HasTwoDrops { + fn drop(&mut self) { + println!("Dropping HasTwoDrops!"); + } +} + +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + println!("Dropping Foo!") + } +} + + +fn main() { + let _x = HasTwoDrops { two: HasDrop2 ,one: HasDrop1,}; + let _foo = Foo; + println!("Running!"); +} +``` + +上面代码虽然长,但是目的其实很单纯,就是为了观察不同情况下的`Drop`,变量级别的、结构体内部字段的, 有几点值得注意: + +- `Drop`特征中的`drop`方法借用了目标的可变引用,而不是拿走了所有权,这里先设置一个悬念,后边会讲 +- 结构体中每个字段都有自己的`Drop` + +来看看输出: +```console +Running! +Dropping Foo! +Dropping HasTwoDrops! +Dropping HasDrop1! +Dropping HasDrop2! +``` + +嗯,结果符合预期,每个资源都成功的执行了收尾工作,虽然`println!`这种收尾工作毫无意义 = , = + +#### Drop的顺序 +观察以上输出,我们可以得出以下关于`Drop`顺序的结论 + +- **变量级别,按照逆序的方式**,,`_x`在`_foo`之前创建,因此`_x`在`_foo`之后被drop +- **结构体内部,按照顺序的方式**, 结构体`_x`中的字段按照定义中的顺序依次`drop` + +#### 没有实现Drop的结构体 +实际上,就算你不为`_x`结构体实现`Drop`特征,它内部的两个字段依然会调用`drop`,移除以下代码,并观察输出: +```rust +impl Drop for HasTwoDrops { + fn drop(&mut self) { + println!("Dropping HasTwoDrops!"); + } +} +``` + +原因在于,Rust自动为几乎所有类型都实现了`Drop`特征,因此就算你不手动为结构体实现`Drop`,它依然会调用默认实现的`drop`函数,同时再调用每个字段的`drop`方法,最终打印出: +```cnosole +Dropping HasDrop1! +Dropping HasDrop2! +``` + +## 手动回收 +当使用智能指针来管理锁的时候,你可能希望提前释放这个锁,然后让其它代码能及时获得锁,此时就需要提前去手动`drop`。 +但是在之前我们提到一个悬念,就是`Drop::drop`只是借用了目标值的可变引用,就算你提前调用了该方法,但是后面的代码依然可以使用目标值,这就会访问一个并不存在的值,非常不安全,好在Rust会阻止你: +```rust +#[derive(Debug)] +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + println!("Dropping Foo!") + } +} + +fn main() { + let foo = Foo; + foo.drop(); + println!("Running!:{:?}", foo); +} +``` + +报错如下: +```console +error[E0040]: explicit use of destructor method + --> src/main.rs:37:9 + | +37 | foo.drop(); + | ----^^^^-- + | | | + | | explicit destructor calls not allowed + | help: consider using `drop` function: `drop(foo)` +``` + +如上所示,编译器直接阻止了我们调用`Drop`特征的`drop`方法,原因是该方法是析构函数,这是一个用来清理实例的通用编程概念,对于Rust而言,不允许显式的调用析构函数。好在在报错的同时,编译器还给出了一个提示:使用`drop`函数。 + + +针对编译器提示的`drop`函数,我们可以大胆推测下:它能够拿走目标值的所有权。现在来看看这个猜测正确与否,以下是`std::mem::drop`函数的签名: +```rust +pub fn drop(_x: T) +``` + +如上所示,`drop`函数确实拿走了目标值的所有权,来验证下: +```rust +fn main() { + let foo = Foo; + drop(foo); + // 以下代码会报错:借用了所有权被转移的值 + // println!("Running!:{:?}", foo); +} +``` + +Bingo,完美拿走了所有权,而且这种实现保证了后续的使用必定会导致编译错误,因此非常安全! + +细心的同学可能已经注意到,这里直接调用了`drop`函数,并没有引入任何模块信息,原因是该函数在[`std::prelude`](../../appendix/prelude.md)里。 + + +## Drop使用场景 +对于Drop而言,主要有两个功能: + +- 回收内存资源 +- 执行一些收尾工作 + +对于第二点,在之前我们已经详细介绍过,因此这里主要对第一点进行下简单说明。 + +在绝大多数情况下,我们都无需手动去`drop`以回收内存资源,因为Rust会自动帮我们完成这些工作,它甚至会对复杂类型的每个字段都单独的调用`drop`进行回收!但是确实有极少数情况,需要你自己来回收资源的,例如文件描述符、网络socket等,当这些超出作用域不再使用时,就需要进行关闭以释放相关的资源,在这些情况下,就需要使用者自己来解决`Drop`的问题。 + + +## 互斥的Copy和Drop +我们无法为一个类型同时实现`Copy`和`Drop`特征。因为实现了`Copy`的特征会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了`Copy`的类型无法拥有析构函数。 +```rust +#[derive(Copy)] +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + println!("Dropping Foo!") + } +} +``` +以下代码报错如下: +```console +error[E0184]: the trait `Copy` may not be implemented for this type; the type has a destructor + --> src/main.rs:24:10 + | +24 | #[derive(Copy)] + | ^^^^ Copy not allowed on types with destructors +``` + + +## 总结 +`Drop`可以用于许多方面,来使得资源清理及收尾工作变得方便和安全,甚至可以用其创建我们自己的内存分配器!通过`Drop`特征和 Rust 所有权系统,你无需担心之后的代码清理,Rust 会自动考虑这些问题。 + +我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保`drop`只会在值不再被使用时被调用一次。 \ No newline at end of file From c2d06809a0a1766dba79e1b526e3e5dc88fa5e5b Mon Sep 17 00:00:00 2001 From: sunface Date: Mon, 3 Jan 2022 22:07:31 +0800 Subject: [PATCH 2/8] =?UTF-8?q?add=E8=B4=A1=E7=8C=AE=E8=80=85=E8=87=B4?= =?UTF-8?q?=E8=B0=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5cd410da..da44fdca 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,17 @@ 总之在写作过程中我们始终铭记初心:为中国用户打造一本**全面的、深入的、持续更新的**Rust教程。 新手用来入门,老手用来提高,高手用来提升生产力。 + +### Contributors +非常感谢本教程的所有贡献者们,你们的任何一处修改都将使教程内容变得更加优秀! + +尤其感谢@1132719438和@codemystery,花费大量时间贡献了多处fix甚至是高质量的内容优化,非常感动,再次感谢~~ + +### 开源说明 +Rust语言圣经是**完全开源**的电子书, 每个章节都至少用时4-6个小时才能初步完稿,牺牲了大量休闲娱乐、陪伴家人的时间,还没有任何钱赚,**如果大家觉得这本书作者真的用心了,希望你能帮我们点一个`star`**,感激不尽:) + +在开源版权上,我们选择了[No License](https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwigkv-KtMT0AhXFdXAKHdI4BCcQFnoECAQQAw&url=https%3A%2F%2Fchoosealicense.com%2Fno-permission%2F&usg=AOvVaw3M2Q4IbdhnpJ2K71TF7SPB),这意味着读者可以随意的fork和阅读,但是**不能私下修改后再包装分发**,如果有这方面的需求,请联系我们,我们不会收钱,只是希望知道谁通过什么方式分发了这本书的部分内容,望理解. + ### 感谢 本书在内容上部分借鉴了以下书籍,特此感谢: - [Rust Book](https://doc.rust-lang.org/book) @@ -27,11 +38,6 @@ - [Async Book](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) 详细清单参见[这里](https://github.com/sunface/rust-course/blob/main/writing-material/books.md) -### 开源说明 -Rust语言圣经是**完全开源**的电子书, 每个章节都至少用时4-6个小时才能初步完稿,牺牲了大量休闲娱乐、陪伴家人的时间,还没有任何钱赚,**如果大家觉得这本书作者真的用心了,希望你能帮我们点一个`star`**,感激不尽:) - -在开源版权上,我们选择了[No License](https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwigkv-KtMT0AhXFdXAKHdI4BCcQFnoECAQQAw&url=https%3A%2F%2Fchoosealicense.com%2Fno-permission%2F&usg=AOvVaw3M2Q4IbdhnpJ2K71TF7SPB),这意味着读者可以随意的fork和阅读,但是**不能私下修改后再包装分发**,如果有这方面的需求,请联系我们,我们不会收钱,只是希望知道谁通过什么方式分发了这本书的部分内容,望理解. - ### Rust社区 与国外的Rust发展如火如荼相比,国内的近况不是特别理想。导致目前这种状况的原因,我个人认为有以下几点原因: @@ -41,3 +47,4 @@ Rust语言圣经是**完全开源**的电子书, 每个章节都至少用时4-6 为此,我整了一本书和一个社区,欢迎大家的加入: - QQ群:1009730433 + From 3e679b8d0c9a218a43735204c3d0d61bcf62c28b Mon Sep 17 00:00:00 2001 From: sunface Date: Mon, 3 Jan 2022 22:08:33 +0800 Subject: [PATCH 3/8] =?UTF-8?q?add=E8=B4=A1=E7=8C=AE=E8=80=85=E8=87=B4?= =?UTF-8?q?=E8=B0=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da44fdca..309e69cb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ ### Contributors 非常感谢本教程的所有贡献者们,你们的任何一处修改都将使教程内容变得更加优秀! -尤其感谢@1132719438和@codemystery,花费大量时间贡献了多处fix甚至是高质量的内容优化,非常感动,再次感谢~~ +尤其感谢[@1132719438](https://github.com/1132719438)和[@codemystery](https://github.com/codemystery),花费大量时间贡献了多处fix甚至是高质量的内容优化,非常感动,再次感谢~~ ### 开源说明 Rust语言圣经是**完全开源**的电子书, 每个章节都至少用时4-6个小时才能初步完稿,牺牲了大量休闲娱乐、陪伴家人的时间,还没有任何钱赚,**如果大家觉得这本书作者真的用心了,希望你能帮我们点一个`star`**,感激不尽:) From cfd5b42bf3ad6c36c77c743ab6b6f955829d0fca Mon Sep 17 00:00:00 2001 From: sunface Date: Mon, 3 Jan 2022 22:12:59 +0800 Subject: [PATCH 4/8] =?UTF-8?q?add=E8=B4=A1=E7=8C=AE=E8=80=85=E8=87=B4?= =?UTF-8?q?=E8=B0=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 309e69cb..00f0f043 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ ### Contributors 非常感谢本教程的所有贡献者们,你们的任何一处修改都将使教程内容变得更加优秀! -尤其感谢[@1132719438](https://github.com/1132719438)和[@codemystery](https://github.com/codemystery),花费大量时间贡献了多处fix甚至是高质量的内容优化,非常感动,再次感谢~~ +尤其感谢[@1132719438](https://github.com/1132719438)、[@codemystery](https://github.com/codemystery)和[@mg-chao](https://github.com/mg-chao),花费大量时间贡献了多处fix甚至是高质量的内容优化,非常感动,再次感谢~~ ### 开源说明 Rust语言圣经是**完全开源**的电子书, 每个章节都至少用时4-6个小时才能初步完稿,牺牲了大量休闲娱乐、陪伴家人的时间,还没有任何钱赚,**如果大家觉得这本书作者真的用心了,希望你能帮我们点一个`star`**,感激不尽:) From 19e6a69de88a4c8543ea69de6c54308426bb4265 Mon Sep 17 00:00:00 2001 From: mg-chao Date: Tue, 4 Jan 2022 11:18:13 +0800 Subject: [PATCH 5/8] translate: move_semantics --- exercise/exercises/move_semantics/README.md | 8 +-- .../move_semantics/move_semantics1.rs | 4 +- .../move_semantics/move_semantics2.rs | 6 +- .../move_semantics/move_semantics3.rs | 6 +- .../move_semantics/move_semantics4.rs | 9 ++- .../move_semantics/move_semantics5.rs | 5 +- exercise/info.toml | 61 ++++++++----------- 7 files changed, 45 insertions(+), 54 deletions(-) diff --git a/exercise/exercises/move_semantics/README.md b/exercise/exercises/move_semantics/README.md index 54ddd8e6..ec50ba1d 100644 --- a/exercise/exercises/move_semantics/README.md +++ b/exercise/exercises/move_semantics/README.md @@ -1,10 +1,10 @@ -# Move Semantics +# 移动语义(Move Semantics) -These exercises are adapted from [pnkfelix](https://github.com/pnkfelix)'s [Rust Tutorial](https://pnkfelix.github.io/rust-examples-icfp2014/) -- Thank you Felix!!! +这些练习改编自 [pnkfelix](https://github.com/pnkfelix) 的 [Rust Tutorial](https://pnkfelix.github.io/rust-examples-icfp2014/) -- 谢谢 Felix !!! -## Further information +## 更多信息 -For this section, the book links are especially important. +以下书籍中的内容对于当前的学习尤其重要。 - [Ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) - [Reference and borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) diff --git a/exercise/exercises/move_semantics/move_semantics1.rs b/exercise/exercises/move_semantics/move_semantics1.rs index e2f5876d..69abae9c 100644 --- a/exercise/exercises/move_semantics/move_semantics1.rs +++ b/exercise/exercises/move_semantics/move_semantics1.rs @@ -1,5 +1,5 @@ // move_semantics1.rs -// Make me compile! Execute `rustlings hint move_semantics1` for hints :) +// 让我能够编译!执行 `rustex hint move_semantics1` 获取提示 :) // I AM NOT DONE @@ -8,7 +8,7 @@ fn main() { let vec1 = fill_vec(vec0); - println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); + println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);// 译:"{} 长度为 {} 内容是 `{:?}`" vec1.push(88); diff --git a/exercise/exercises/move_semantics/move_semantics2.rs b/exercise/exercises/move_semantics/move_semantics2.rs index bd21fbb7..ff52e032 100644 --- a/exercise/exercises/move_semantics/move_semantics2.rs +++ b/exercise/exercises/move_semantics/move_semantics2.rs @@ -1,6 +1,6 @@ // move_semantics2.rs -// Make me compile without changing line 13! -// Execute `rustlings hint move_semantics2` for hints :) +// 在不更改第 13 行的要求下通过编译! +// 执行 `rustex hint move_semantics2` 获取提示 :) // I AM NOT DONE @@ -9,7 +9,7 @@ fn main() { let mut vec1 = fill_vec(vec0); - // Do not change the following line! + // 不要更改下面那行! println!("{} has length {} content `{:?}`", "vec0", vec0.len(), vec0); vec1.push(88); diff --git a/exercise/exercises/move_semantics/move_semantics3.rs b/exercise/exercises/move_semantics/move_semantics3.rs index 43fef74f..d9fea4e2 100644 --- a/exercise/exercises/move_semantics/move_semantics3.rs +++ b/exercise/exercises/move_semantics/move_semantics3.rs @@ -1,7 +1,7 @@ // move_semantics3.rs -// Make me compile without adding new lines-- just changing existing lines! -// (no lines with multiple semicolons necessary!) -// Execute `rustlings hint move_semantics3` for hints :) +// 在不添加新行仅改变已有行的要求下通过编译! +// (也不允许有多个分号的行!) +// 执行 `rustex hint move_semantics3` 获取提示 :) // I AM NOT DONE diff --git a/exercise/exercises/move_semantics/move_semantics4.rs b/exercise/exercises/move_semantics/move_semantics4.rs index 2a23c710..8f7f0d52 100644 --- a/exercise/exercises/move_semantics/move_semantics4.rs +++ b/exercise/exercises/move_semantics/move_semantics4.rs @@ -1,8 +1,7 @@ // move_semantics4.rs -// Refactor this code so that instead of having `vec0` and creating the vector -// in `fn main`, we create it within `fn fill_vec` and transfer the -// freshly created vector from fill_vec to its caller. -// Execute `rustlings hint move_semantics4` for hints! +// 重构这段代码,做到删除 `vec0` ,并在 `fn fill_vec` 而非 `fn main` 中创建 vector , +// 然后将新创建的 vector 从 `fill_vec` 转移到其调用者。 +// 执行 `rustex hint move_semantics4` 获取提示 :) // I AM NOT DONE @@ -18,7 +17,7 @@ fn main() { println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); } -// `fill_vec()` no longer takes `vec: Vec` as argument +// `fill_vec()` 不再获取 `vec: Vec` 参数 fn fill_vec() -> Vec { let mut vec = vec; diff --git a/exercise/exercises/move_semantics/move_semantics5.rs b/exercise/exercises/move_semantics/move_semantics5.rs index c4704f9e..d57a1261 100644 --- a/exercise/exercises/move_semantics/move_semantics5.rs +++ b/exercise/exercises/move_semantics/move_semantics5.rs @@ -1,7 +1,6 @@ // move_semantics5.rs -// Make me compile only by reordering the lines in `main()`, but without -// adding, changing or removing any of them. -// Execute `rustlings hint move_semantics5` for hints :) +// 只通过重新排列 `main()` 中的已有行来完成编译,并且不能增加、更改或删除任何行 +// 执行 `rustex hint move_semantics5` 获取提示 :) // I AM NOT DONE diff --git a/exercise/info.toml b/exercise/info.toml index 99833a3e..88c9ae5d 100644 --- a/exercise/info.toml +++ b/exercise/info.toml @@ -152,64 +152,57 @@ name = "move_semantics1" path = "exercises/move_semantics/move_semantics1.rs" mode = "compile" hint = """ -So you've got the "cannot borrow immutable local variable `vec1` as mutable" error on line 13, -right? The fix for this is going to be adding one keyword, and the addition is NOT on line 13 -where the error is.""" +在第 13 行有个 "cannot borrow immutable local variable `vec1` as mutable"* 错误,对吗? +修复错误的方法是添加一个关键词,并且添加的位置不在报错的第 13 行上。 + +译注:不能将不可变的局部变量 `vec1` 借用为可变变量""" [[exercises]] name = "move_semantics2" path = "exercises/move_semantics/move_semantics2.rs" mode = "compile" hint = """ -So `vec0` is being *moved* into the function `fill_vec` when we call it on -line 10, which means it gets dropped at the end of `fill_vec`, which means we -can't use `vec0` again on line 13 (or anywhere else in `main` after the -`fill_vec` call for that matter). We could fix this in a few ways, try them -all! -1. Make another, separate version of the data that's in `vec0` and pass that - to `fill_vec` instead. -2. Make `fill_vec` borrow its argument instead of taking ownership of it, - and then copy the data within the function in order to return an owned - `Vec` -3. Make `fill_vec` *mutably* borrow its argument (which will need to be - mutable), modify it directly, then not return anything. Then you can get rid - of `vec1` entirely -- note that this will change what gets printed by the - first `println!`""" +当我们在第 10 行调用 `fill_vec` 时,`vec0' 被 *移动(moved)* 到 +函数 `fill_vec` 中,这意味着它会在 `fill_vec` 函数的末尾被丢弃,同时也 +导致了我们不能在第 13 行再次使用 `vec0`(或在 `main` 中调用 `fill_vec` 后的任何地方)。 +我们可以用几种方法来解决这个问题,都试一试吧! +1. 做一个 `vec0` 数据的拷贝,并将其传递给 `fill_vec` 。 +2. 让 `fill_vec` 通过借用而不是获取所有权的方式获取参数,然后在函数中复制一份数据,以便返回 + 一个具有所有权的 `Vec` 变量。 +3. 让 `fill_vec` 借用可变参数(参数也需要可变),直接进行操作,然后不返回任何东西。接着你需要 + 完全地去掉 `vec1`——但注意,这也将改变第一个 `println!` 打印出内容。""" [[exercises]] name = "move_semantics3" path = "exercises/move_semantics/move_semantics3.rs" mode = "compile" hint = """ -The difference between this one and the previous ones is that the first line -of `fn fill_vec` that had `let mut vec = vec;` is no longer there. You can, -instead of adding that line back, add `mut` in one place that will change -an existing binding to be a mutable binding instead of an immutable one :)""" +与之前不同:`fn fill_vec` 第一行的 `let mut vec = vec;` 现在已经不存在了。 +你可以在某个地方添加 `mut` 以使现有的不可变绑定变得可变,而非把不同的那一行加回去 :)""" [[exercises]] name = "move_semantics4" path = "exercises/move_semantics/move_semantics4.rs" mode = "compile" hint = """ -Stop reading whenever you feel like you have enough direction :) Or try -doing one step and then fixing the compiler errors that result! -So the end goal is to: - - get rid of the first line in main that creates the new vector - - so then `vec0` doesn't exist, so we can't pass it to `fill_vec` - - we don't want to pass anything to `fill_vec`, so its signature should - reflect that it does not take any arguments - - since we're not creating a new vec in `main` anymore, we need to create - a new vec in `fill_vec`, similarly to the way we did in `main`""" +只要你觉得有确切的目标,就可以停止阅读 :) 或者试着做一个步骤,然后修复编译错误。 +因此,目标有: + - 去掉 main 中创建新 vector 的第一行 + - 所以 `vec0` 并不存在,所以我们不能把它传给 `fill_vec` 。 + - 我们不需要向 `fill_vec` 传递任何东西,所以它的签名应该反映出它不接受任何参数*。 + - 由于我们已不在 `main` 创建 vector ,所以需要在 `fill_vec` 中创建一个新的 vector, + 类似于 `main` 中的做法。 + +译注:练习中 fill_vec 的函数签名已经没有接受参数了,所以估计是在调用的地方""" [[exercises]] name = "move_semantics5" path = "exercises/move_semantics/move_semantics5.rs" mode = "compile" hint = """ -Carefully reason about the range in which each mutable reference is in -vogue. Does it help to update the value of referent (x) immediately after -the mutable reference is taken? Read more about 'Mutable References' -in the book's section References and Borrowing': +仔细推敲每个可变引用的使用范围。 +在获取可变引用后是否能够立即更新引用(x)的值? +在本书的 'References and Borrowing' 部分了解更多关于 'Mutable References' 的信息。 https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references. """ From e7446d47b118d1e8747b1ef3e129b9f6fe654198 Mon Sep 17 00:00:00 2001 From: mg-chao Date: Tue, 4 Jan 2022 11:26:12 +0800 Subject: [PATCH 6/8] fix --- exercise/info.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercise/info.toml b/exercise/info.toml index 88c9ae5d..68e194f6 100644 --- a/exercise/info.toml +++ b/exercise/info.toml @@ -188,9 +188,9 @@ hint = """ 只要你觉得有确切的目标,就可以停止阅读 :) 或者试着做一个步骤,然后修复编译错误。 因此,目标有: - 去掉 main 中创建新 vector 的第一行 - - 所以 `vec0` 并不存在,所以我们不能把它传给 `fill_vec` 。 - - 我们不需要向 `fill_vec` 传递任何东西,所以它的签名应该反映出它不接受任何参数*。 - - 由于我们已不在 `main` 创建 vector ,所以需要在 `fill_vec` 中创建一个新的 vector, + - 所以 `vec0` 并不存在,不能再把它传给 `fill_vec` 。 + - 现已不需要向 `fill_vec` 传递任何东西,所以它的(函数)签名应该反映出它不接受任何参数*。 + - 由于已不在 `main` 创建 vector ,所以需要在 `fill_vec` 中创建一个新的 vector, 类似于 `main` 中的做法。 译注:练习中 fill_vec 的函数签名已经没有接受参数了,所以估计是在调用的地方""" From ec904ffcf92bb7f4270c0c07beaf36a7745ce870 Mon Sep 17 00:00:00 2001 From: mg-chao Date: Tue, 4 Jan 2022 11:27:38 +0800 Subject: [PATCH 7/8] fix --- exercise/info.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercise/info.toml b/exercise/info.toml index 68e194f6..d94f55e2 100644 --- a/exercise/info.toml +++ b/exercise/info.toml @@ -188,7 +188,7 @@ hint = """ 只要你觉得有确切的目标,就可以停止阅读 :) 或者试着做一个步骤,然后修复编译错误。 因此,目标有: - 去掉 main 中创建新 vector 的第一行 - - 所以 `vec0` 并不存在,不能再把它传给 `fill_vec` 。 + - 所以 `vec0` 已不存在了,不能再把它传给 `fill_vec` 。 - 现已不需要向 `fill_vec` 传递任何东西,所以它的(函数)签名应该反映出它不接受任何参数*。 - 由于已不在 `main` 创建 vector ,所以需要在 `fill_vec` 中创建一个新的 vector, 类似于 `main` 中的做法。 From 4672bd1fe74a3c790f973024750f571424c04807 Mon Sep 17 00:00:00 2001 From: sunface Date: Tue, 4 Jan 2022 12:32:51 +0800 Subject: [PATCH 8/8] =?UTF-8?q?add=E5=AF=B9=E6=8A=97=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 8196 -> 10244 bytes course-book/contents/SUMMARY.md | 22 +-- .../contents/advance/lifetime/advance.md | 2 +- .../advance/multi-threads/send-sync.md | 3 + .../lifetime/closure-with-static.md | 163 ++++++++++++++++++ 5 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 course-book/contents/fight-with-compiler/lifetime/closure-with-static.md diff --git a/.DS_Store b/.DS_Store index 4fa248e9937e720b46b44870a1d8c03c50936532..75e44e076e39372f121bc2271225f28877d68305 100644 GIT binary patch literal 10244 zcmeHMeQX>@6@PEzkG=R3uM@j*TyJ~wr6O%?_hKiF+cf0-)z(Q9r#`zr$4)MLw;Owt zy7EM|_nw+(^4`KHi%*^Jd=cZ{7j`4ixmg05JfN>0wl90I1mv&`LC%SUY>xq)k+V z^t97Mc?H0M^Uwu3qV^i@YDLuoRSSHOEFjTfnE%$$vjwu`wPFAD!5}a8!Lz)D!K~$E z$;ngMdCTiX3|^)yrapyv6b^M=iabXm0BM{Os)In6aj7j450Ms$Q~`I1sS6B^b{-{Fm9 zjpH4vI>ox{*L6OJw$`1EYD%4um7mEOMKSFK)2D@@r6N-BOxq z-@(iHBm6P`9N)k<@i+Ke{1g5S|BmlUwbBl$RoW?aNS#ub)GZy5`lNfLgVJ$nR7y*l zbWU=lB{2bsIT}l+1S!6yWuC)v+p&#EJb6Mi#FIw@4jJ}9Kcrv=F2E{037?0r!gt}P z@Fx5P-oq_;6L#R9V4g`F!6P`0XHdg)LcXL|4f+cR8%{7Jx#D|Y&R8u~El{<<|D^@k z`4FRr(PThZ1?5Kvy%>aG?F>TjM0vUg@VZz)lL1{7l#7xK7bPl0F}Px&5U2VWV<(yn z=&GPX98ib@2AVOrp&-{zaxu;hm=v`7u3DgKfwdNBqN}s_ooPWe;Pl7PvPhAX#(G0 z#4q8O@dbR5p!c`%5`G)Mhu_Cn@P`2i|0O~2Kj7P9E0^HyvGT2rf8KasFQi=Ca_m{+ zVjq;o(CycNC5m7fbjiqp2_EIV5zEO5FJNCMWKjCq_jsZlK%^wyP4{I7@?e!q5M&4F z4oTF)ARH%He+-7pIeFh<&ITS$E6`}zNVPMrTsauAGs? z>=4#k2Rdn+Cm9Ep%XRU&gmuZpxkOLNUgiAwPDS4hFw6V(Xhs@IEkie1Os|l7tYSv> zG#Z!q*$ilPD!7@B@R%Yn2E%0EA!gxg_8*?{7V5n~Bl5|{|LlA`8WW3_bKc6^&ZD$ylrVsF^c^Zs zY!6&_o)w;4-b1x?Jqu90AL?koF>f?z|5coIaqZIdP;L$&7L&|mQV30>!>~8_`Z`bg`By{)V!9XU~&BLTf*Zg zl($>t6o`uqKs-4ivhtdk^!pzF^JpZcMXuQN8(aw1`S6C_U<1kjwPW-qdOKr1d;9*) z57GuR{8wD1t?e4FQrcc0UJv2_3Ce!`tm`VFp!a$%^U;l6h8(RtRDOhl<#U9to`9#| z3vh{k5WEh5guh}<5Gvn?hww1nP#&V60Xgw2KxmMPIHWRE&Rpe2OtoTCF&6(HzV`qB z8*%E@IaMuCwLoA2tQkrT4N#$Y09IOt#oA-^oS}y$#I0HioL$PO0 zesWSye$r+^4l$O^3LL%6oIp7c;NXUcsqs8zW)#}YBVo%jSwKW^a+J*M$#*2(C)>zM z^OOY_<>ln(r2~~SPWBOw=VoO{X2@qqVMt{to~$P#KUq;kdU6g>(?b!X&CQ~F83mca tW-~}|18G-~(x: *const T) -> &'a T { 我们在实际应用中,要尽量避免这种无界生命周期。最简单的避免无界生命周期的方式就是在函数声明中运用生命周期消除规则。**若一个输出生命周期被消除了,那么必定因为有一个输入生命周期与之对应**。 -## 生命周期约束 +## 生命周期约束HRTB 生命周期约束跟特征约束类似,都是通过形如`'a: 'b`的语法,来说明两个生命周期的长短关系。 #### 'a: 'b diff --git a/course-book/contents/advance/multi-threads/send-sync.md b/course-book/contents/advance/multi-threads/send-sync.md index d32b59c3..fbfc7aa1 100644 --- a/course-book/contents/advance/multi-threads/send-sync.md +++ b/course-book/contents/advance/multi-threads/send-sync.md @@ -1 +1,4 @@ # Send、Sync(todo) + + +https://www.reddit.com/r/learnrust/comments/rufs0n/can_a_reference_to_a_type_mytype_implement/ \ No newline at end of file diff --git a/course-book/contents/fight-with-compiler/lifetime/closure-with-static.md b/course-book/contents/fight-with-compiler/lifetime/closure-with-static.md new file mode 100644 index 00000000..5b1446bc --- /dev/null +++ b/course-book/contents/fight-with-compiler/lifetime/closure-with-static.md @@ -0,0 +1,163 @@ +# 当闭包碰到特征对象1 +特征对象是一个好东西,闭包也是一个好东西,但是如果两者你都想要时,可能就会火星撞地球,boom! 至于这两者为何会勾搭到一起?考虑一个常用场景:使用闭包作为回调函数. + +## 学习目标 +如何使用闭包作为特征对象,并解决以下错误:`the parameter type `impl Fn(&str) -> Res` may not live long enough` + +## 报错的代码 +在下面代码中,我们通过闭包实现了一个简单的回调函数(错误代码已经标注): +```rust +pub struct Res<'a> { + value: &'a str, +} + +impl<'a> Res<'a> { + pub fn new(value: &str) -> Res { + Res { value } + } +} + +pub struct Container<'a> { + name: &'a str, + callback: Option Res>>, +} + +impl<'a> Container<'a> { + pub fn new(name: &str) -> Container { + Container { + name, + callback: None, + } + } + + pub fn set(&mut self, cb: impl Fn(&str) -> Res) { + self.callback = Some(Box::new(cb)); + } +} + +fn main() { + let mut inl = Container::new("Inline"); + + inl.set(|val| { + println!("Inline: {}", val); + Res::new("inline") + }); + + if let Some(cb) = inl.callback { + cb("hello, world"); + } +} +``` + +从第一感觉来说,报错属实不应该,因为我们连引用都没有用,生命周期都不涉及,怎么就报错了?在继续深入之前,先来观察下该闭包是如何被使用的: +```rust +callback: Option Res>>, +``` + +众所周知,闭包跟哈姆雷特一样,每一个都有[自己的类型](../../advance/functional-programing/closure.md#闭包作为函数返回值),因此我们无法通过类型标注的方式来声明一个闭包,那么只有一个办法,就是使用特征对象,因此上面代码中,通过`Box`的方式把闭包特征封装成一个特征对象。 + +## 深入挖掘报错原因 +事出诡异必有妖,那接下来我们一起去会会这只妖。 + +#### 特征对象的生命周期 +首先编译器报错提示我们闭包活得不够久,那可以大胆推测,正因为使用了闭包作为特征对象,所以才活得不够久。因此首先需要调查下特征对象的生命周期。 + +首先给出结论:**特征对象隐式的具有`'static`生命周期**。 + +其实在Rust中,`'static`生命周期很常见,例如一个没有引用字段的结构体它其实也是`'static`。当`'static`用于一个类型时,该类型不能包含任何非`'static`引用字段,例如以下结构体: +```rust +struct Foo<'a> { + x : &'a [u8] +}; +``` + +除非`x`字段借用了`'static`的引用,否则`'a`肯定比`'static`要小,那么该结构体实例的生命周期肯定不是`'static`: `'a: 'static`的限制不会被满足([HRTB](../../advance/lifetime/advance.md#生命周期约束HRTB))。 + +对于特征对象来说,它没有包含非`'static`的引用,因此它隐式的具有`'static`生命周期, `Box`就跟`Box`是等价的。 + +#### 'static闭包的限制 +其实以上代码的错误很好解决,甚至编译器也提示了我们: +```console +help: consider adding an explicit lifetime bound...: `impl Fn(&str) -> Res + 'static` +``` + +但是解决问题不是本文的目标,我们还是要继续深挖一下,如果闭包使用了`'static`会造成什么问题。 + +##### 1. 无本地变量被捕获 +```rust +inl.set(|val| { + println!("Inline: {}", val); + Res::new("inline") +}); +``` + +以上代码只使用了闭包中传入的参数,并没有本地变量被捕获,因此`'static`闭包一切OK。 + +##### 2. 有本地变量被捕获 +```rust +let local = "hello".to_string(); + +// 编译错误: 闭包不是'static! +inl.set(|val| { + println!("Inline: {}", val); + println!("{}", local); + Res::new("inline") +}); +``` + +这里我们在闭包中捕获了本地环境变量`local`,因为`local`不是`'static`,那么闭包也不再是`'static`。 + +##### 3. 将本地变量move进闭包 +```rust +let local = "hello".to_string(); + +inl.set(move |val| { + println!("Inline: {}", val); + println!("{}", local); + Res::new("inline") +}); + +// 编译错误: local已经被移动到闭包中,这里无法再被借用 +// println!("{}", local); +``` + +如上所示,你也可以选择将本地变量的所有权`move`进闭包中,此时闭包再次具有`'statci`生命周期 + +##### 4. 非要捕获本地变量的引用? +对于第2种情况,如果非要这么干,那`'static`肯定是没办法了,我们只能给予闭包一个新的生命周期: +```rust +pub struct Container<'a, 'b> { + name: &'a str, + callback: Option Res + 'b>>, +} + +impl<'a, 'b> Container<'a, 'b> { + pub fn new(name: &str) -> Container { + Container { + name, + callback: None, + } + } + + pub fn set(&mut self, cb: impl Fn(&str) -> Res + 'b) { + self.callback = Some(Box::new(cb)); + } +} +``` + +肉眼可见,代码复杂度哐哐哐提升,不得不说`'static`真香! + +友情提示:由此修改引发的一系列错误,需要你自行修复: ) (再次友情小提示,可以考虑把`main`中的`local`变量声明位置挪到`inl`声明位置之前) + +## 姗姗来迟的正确代码 +其实,大家应该都知道该如何修改了,不过出于严谨,我们还是继续给出完整的正确代码: +```rust +pub fn set(&mut self, cb: impl Fn(&str) -> Res + 'static) { +``` + +可能大家觉得我重新定义了`完整`两个字,其实是我不想水篇幅:) + +## 总结 +闭包和特征对象的相爱相杀主要原因就在于特征对象默认具备`'static`的生命周期,同时我们还对什么样的类型具备`'static`进行了简单的分析。 + +同时,如果一个闭包拥有`'static`生命周期,那闭包无法通过引用的方式来捕获本地环境中的变量。如果你想要非要捕获,只能使用非`'static`。