├── .gitignore
├── assets
├── hackerrank-more-like-failrank.png
├── holy-shit-two-cakes.png
└── main.rs
├── license-apache
├── license-mit
├── posts
├── Rust生命周期的常见误解.md
├── common-rust-lifetime-misconceptions.md
├── learning-rust-in-2020.md
└── why-blog.md
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | wip
--------------------------------------------------------------------------------
/assets/hackerrank-more-like-failrank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whfuyn/rust-blog/1adc279f8c5f8f4925e40c30abede73484eb8d05/assets/hackerrank-more-like-failrank.png
--------------------------------------------------------------------------------
/assets/holy-shit-two-cakes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whfuyn/rust-blog/1adc279f8c5f8f4925e40c30abede73484eb8d05/assets/holy-shit-two-cakes.png
--------------------------------------------------------------------------------
/assets/main.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | println!("hopefully github detects this file and categorizes my repo as a Rust repo");
3 | }
4 |
--------------------------------------------------------------------------------
/license-apache:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/license-mit:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/posts/Rust生命周期的常见误解.md:
--------------------------------------------------------------------------------
1 | # Rust生命周期常见误区
2 |
3 | _5月19日, 2020 · 阅读大概需要37分钟 · #rust · #生命周期_
4 |
5 | **目录**
6 | - [介绍](#介绍)
7 | - [误解列表](#误解列表)
8 | - [1) `T` 只包含所有权类型](#1-T-只包含所有权类型)
9 | - [2) 如果 `T: 'static` 那么 `T` 必须在整个程序运行中都是有效的](#2-如果-T-static-那么-T-必须在整个程序运行中都是有效的)
10 | - [3) `&'a T` 和 `T: 'a` 是相同的](#3-a-T-和-T-a-是相同的)
11 | - [4) 我的代码没用到泛型,也不含生命周期](#4-我的代码没用到泛型,也不含生命周期)
12 | - [5) 如果编译能通过,那么我的生命周期标注就是正确的](#5-如果编译能通过,那么我的生命周期标注就是正确的)
13 | - [6) 装箱的trait对象没有生命周期](#6-装箱的trait对象没有生命周期)
14 | - [7) 编译器报错信息会告诉我怎么修改我的代码](#7-编译器报错信息会告诉我怎么修改我的代码)
15 | - [8) 生命周期可以在运行时变长缩短](#8-生命周期可以在运行时变长缩短)
16 | - [9) 将可变引用降级为共享引用是安全的](#9-将可变引用降级为共享引用是安全的)
17 | - [10) 闭包遵循和函数相同的生命周期省略规则](#10-闭包遵循和函数相同的生命周期省略规则)
18 | - [11) `'static` 引用总能强制转换为 `'a` 引用](#11-static-引用总能强制转换为-a-引用)
19 | - [总结](#总结)
20 | - [讨论](#讨论)
21 | - [关注](#关注)
22 |
23 |
24 |
25 | ## 介绍
26 |
27 | 我曾经有过的所有这些对生命周期的误解,现在有很多初学者也深陷于此。
28 | 我用到的术语可能不是标准的,所以下面列了一个表格来解释它们的用意。
29 |
30 |
31 | | 短语 | 意为 |
32 | |-|-|
33 | | `T` | 1) 包含了所有可能类型的集合 _或_
2) 这个集合中的类型 |
34 | | 所有权类型 | 不含引用的类型, 例如 `i32`, `String`, `Vec`, 等 |
35 | | 1) 借用类型 _或_
2) 引用类型 | 不考虑可变性的引用类型, 例如 `&i32`, `&mut i32`, 等 |
36 | | 1) 可变引用 _或_
2) 独占引用 | 独占的可变引用, 即 `&mut T` |
37 | | 1) 不可变引用 _or_
2) 共享引用 | 共享的不可变引用, 即 `&T` |
38 |
39 |
40 |
41 | ## 误解列表
42 |
43 | 简而言之:变量的生命周期指的是这个变量所指的数据可以被编译器静态验证的、在当前内存地址有效期的长度。
44 | 我现在会用大约~8000字来详细地解释一下那些容易误解的地方。
45 |
46 | ### 1) `T` 只包含所有权类型
47 |
48 | 这个误解比起说生命周期,它和泛型更相关,但在Rust中泛型和生命周期是紧密联系在一起的,不可只谈其一。
49 |
50 | 当我刚开始学习Rust的时候,我理解`i32`,`&i32`,和`&mut i32`是不同的类型,也明白泛型变量`T`代表着所有可能类型的集合。
51 | 但尽管这二者分开都懂,当它们结合在一起的时候我却陷入困惑。在我这个Rust初学者的眼中,泛型是这样的运作的:
52 |
53 | | | | | |
54 | |-|-|-|-|
55 | | **类型变量** | `T` | `&T` | `&mut T` |
56 | | **例子** | `i32` | `&i32` | `&mut i32` |
57 |
58 | `T` 包含一切所有权类型; `&T` 包含一切不可变借用类型; `&mut T` 包含一切可变借用类型。
59 | `T`, `&T`, 和 `&mut T` 是不相交的有限集。 简洁明了,符合直觉,但却完全错误。
60 | 下面这才是泛型真正的运作方式:
61 |
62 | | | | | |
63 | |-|-|-|-|
64 | | **类型变量** | `T` | `&T` | `&mut T` |
65 | | **例子** | `i32`, `&i32`, `&mut i32`, `&&i32`, `&mut &mut i32`, ... | `&i32`, `&&i32`, `&&mut i32`, ... | `&mut i32`, `&mut &mut i32`, `&mut &i32`, ... |
66 |
67 | `T`, `&T`, 和 `&mut T` 都是无限集, 因为你可以无限借用一个类型。
68 | `T` 是 `&T` 和 `&mut T`的超集. `&T` 和 `&mut T` 是不相交的集合。
69 | 让我们用几个例子来检验一下这些概念:
70 |
71 | ```rust
72 | trait Trait {}
73 |
74 | impl Trait for T {}
75 |
76 | impl Trait for &T {} // 编译错误
77 |
78 | impl Trait for &mut T {} // 编译错误
79 | ```
80 |
81 | 上面的代码并不能如愿编译:
82 |
83 | ```rust
84 | error[E0119]: conflicting implementations of trait `Trait` for type `&_`:
85 | --> src/lib.rs:5:1
86 | |
87 | 3 | impl Trait for T {}
88 | | ------------------- first implementation here
89 | 4 |
90 | 5 | impl Trait for &T {}
91 | | ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&_`
92 |
93 | error[E0119]: conflicting implementations of trait `Trait` for type `&mut _`:
94 | --> src/lib.rs:7:1
95 | |
96 | 3 | impl Trait for T {}
97 | | ------------------- first implementation here
98 | ...
99 | 7 | impl Trait for &mut T {}
100 | | ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&mut _`
101 | ```
102 |
103 | 编译器不允许我们为`&T`和`&mut T`实现`Trait`,因为这样会与为`T`实现的`Trait`冲突,
104 | `T`本身已经包含了所有`&T`和`&mut T`。下面的代码能够如愿编译,因为`&T`和`&mut T`是不相交的:
105 |
106 | ```rust
107 | trait Trait {}
108 |
109 | impl Trait for &T {} // 编译通过
110 |
111 | impl Trait for &mut T {} // 编译通过
112 | ```
113 |
114 | **要点**
115 | - `T` 是 `&T` 和 `&mut T`的超集
116 | - `&T` 和 `&mut T` 是不相交的集合
117 |
118 |
119 | ### 2) 如果 `T: 'static` 那么 `T` 必须在整个程序运行中都是有效的
120 |
121 | **误解推论**
122 | - `T: 'static` 应该被看作 _" `T` 拥有 `'static` 生命周期 "_
123 | - `&'static T` 和 `T: 'static` 没有区别
124 | - 如果 `T: 'static` 那么 `T` 必须为不可变的
125 | - 如果 `T: 'static` 那么 `T` 只能在编译期创建
126 |
127 | 大部分Rust初学者是从类似下面这个代码示例中接触到 `'static` 生命周期的:
128 |
129 | ```rust
130 | fn main() {
131 | let str_literal: &'static str = "str literal";
132 | }
133 | ```
134 |
135 | 他们被告知 `"str literal"` 是硬编码在编译出来的二进制文件中的,
136 | 并会在运行时被加载到只读内存,所以必须是不可变的且在整个程序的运行中都是有效的,
137 | 这就是它成为 `'static` 的原因。
138 | 而这些观念又进一步被用 `static` 关键字来定义静态变量的规则所加强。
139 |
140 |
141 | ```rust
142 | static BYTES: [u8; 3] = [1, 2, 3];
143 | static mut MUT_BYTES: [u8; 3] = [1, 2, 3];
144 |
145 | fn main() {
146 | MUT_BYTES[0] = 99; // 编译错误,修改静态变量是unsafe的
147 |
148 | unsafe {
149 | MUT_BYTES[0] = 99;
150 | assert_eq!(99, MUT_BYTES[0]);
151 | }
152 | }
153 | ```
154 |
155 | 认为静态变量
156 | - 只可以在编译期创建
157 | - 必须是不可变的,修改它们是unsafe的
158 | - 在整个程序的运行过程中都是有效的
159 |
160 | `'static` 生命周期大概是以静态变量的默认生命周期命名的,对吧?
161 | 那么有理由认为`'static`生命周期也应该遵守相同的规则,不是吗?
162 |
163 | 是的,但拥有`'static`生命周期的类型与`'static`约束的类型是不同的。
164 | 后者能在运行时动态分配,可以安全地、自由地修改,可以被drop,
165 | 还可以有任意长度的生命周期。
166 |
167 | 在这个点,很重要的是要区分 `&'static T` 和 `T: 'static`。
168 |
169 | `&'static T`是对某个`T`的不可变引用,这个引用可以被无限期地持有直到程序结束。
170 | 这只可能发生在`T`本身不可变且不会在引用被创建后移动的情况下。
171 | `T`并不需要在编译期就被创建,因为我们可以在运行时动态生成随机数据,
172 | 然后以内存泄漏为代价返回`'static`引用,例如:
173 |
174 |
175 | ```rust
176 | use rand;
177 |
178 | // 在运行时生成随机&'static str
179 | fn rand_str_generator() -> &'static str {
180 | let rand_string = rand::random::().to_string();
181 | Box::leak(rand_string.into_boxed_str())
182 | }
183 | ```
184 |
185 | `T: 'static` 是指`T`可以被无限期安全地持有直到程序结束。
186 | `T: 'static`包括所有`&'static T`,此外还包括所有的所有权类型,比如`String`, `Vec`等。
187 | 数据的所有者能够保证数据只要还被持有就不会失效,因此所有者可以无限期安全地持有该数据直到程序结束。
188 | `T: 'static`应该被看作“`T`受`'static`生命周期约束”而非“`T`有着`'static`生命周期”。
189 | 这段代码能帮我们阐释这些概念:
190 |
191 |
192 | ```rust
193 | use rand;
194 |
195 | fn drop_static(t: T) {
196 | std::mem::drop(t);
197 | }
198 |
199 | fn main() {
200 | let mut strings: Vec = Vec::new();
201 | for _ in 0..10 {
202 | if rand::random() {
203 | // 所有字符串都是随机生成的
204 | // 并且是在运行时动态申请的
205 | let string = rand::random::().to_string();
206 | strings.push(string);
207 | }
208 | }
209 |
210 | // 这些字符串都是所有权类型,所以它们满足'static约束
211 | for mut string in strings {
212 | // 这些字符串都是可以修改的
213 | string.push_str("a mutation");
214 | // 这些字符串都是可以被drop的
215 | drop_static(string); // 编译通过
216 | }
217 |
218 | // 这些字符串都在程序结束之前失效
219 | println!("i am the end of the program");
220 | }
221 | ```
222 |
223 | **要点**
224 | - `T: 'static` 应该被看作 _“`T`受`'static`生命周期约束”_
225 | - 如果 `T: 'static` 那么`T`可以是有着`'static`生命周期的借用类型
226 | - 由于 `T: 'static` 包括了所有权类型,这意味着`T`
227 | - 可以在运行时动态分配
228 | - 不一定要在整个程序的运行过程中都有效
229 | - 可以被安全地、自由地修改
230 | - 可以在运行时被动态drop掉
231 | - 可以有不同长度的生命周期
232 |
233 |
234 | ### 3) `&'a T` 和 `T: 'a` 是相同的
235 |
236 | 这个误解是上一个的泛化版本。
237 |
238 | `&'a T` 不光要求,同时也隐含着 `T: 'a`, 因为如果`T`本身都不能在`'a`内有效,
239 | 那对`T`的有`'a`生命周期的引用也不可能是有效的。
240 | 例如,Rust编译器从来不会允许创建`&'static Ref<'a, T>`这个类型,因为如果`Ref`只在`'a`内有效,我们不可能弄出一个对它的`'static`的引用。
241 |
242 | `T: 'a`包括了所有`&'a T`,但反过来不对。
243 |
244 | ```rust
245 | // 只接受以'a约束的引用类型
246 | fn t_ref<'a, T: 'a>(t: &'a T) {}
247 |
248 | // 接受所有以'a约束的类型
249 | fn t_bound<'a, T: 'a>(t: T) {}
250 |
251 | // 包含引用的所有权类型
252 | struct Ref<'a, T: 'a>(&'a T);
253 |
254 | fn main() {
255 | let string = String::from("string");
256 |
257 | t_bound(&string); // 编译通过
258 | t_bound(Ref(&string)); // 编译通过
259 | t_bound(&Ref(&string)); // 编译通过
260 |
261 | t_ref(&string); // 编译通过
262 | t_ref(Ref(&string)); // 编译错误, 期待接收一个引用,但收到一个结构体
263 | t_ref(&Ref(&string)); // 编译通过
264 |
265 | // string变量是以'static约束的,也满足'a约束
266 | t_bound(string); // 编译通过
267 | }
268 | ```
269 |
270 | **要点**
271 | - `T: 'a` 比起 `&'a T`更泛化也更灵活
272 | - `T: 'a` 接受所有权类型、包含引用的所有权类型以及引用
273 | - `&'a T` 只接受引用
274 | - 如果 `T: 'static` 那么 `T: 'a`, 因为对于所有`'a`都有`'static` >= `'a`
275 |
276 |
277 |
278 | ### 4) 我的代码没用到泛型,也不含生命周期
279 |
280 | **误解推论**
281 | - 避免使用泛型和生命周期是可能的
282 |
283 | 这种安慰性的误解的存在是由于Rust的生命周期省略规则,
284 | 这些规则让你能够在函数中省略掉生命周期记号,
285 | 因为Rust的借用检查器能根据以下规则将它们推导出来:
286 | - 每个传入的引用都会有一个单独的生命周期
287 | - 如果只有一个传入的生命周期,那么它将被应用到所有输出的引用上
288 | - 如果有多个传入的生命周期,但其中一个是`&self`或者`&mut self`,那么这个生命周期将会被应用到所有输出的引用上
289 | - 除此之外的输出的生命周期都必须显示标注出来
290 |
291 |
292 | 如果一时间难以想明白这么多东西,那让我们来看一些例子:
293 |
294 | ```rust
295 | // 省略
296 | fn print(s: &str);
297 |
298 | // 展开
299 | fn print<'a>(s: &'a str);
300 |
301 | // 省略
302 | fn trim(s: &str) -> &str;
303 |
304 | // 展开
305 | fn trim<'a>(s: &'a str) -> &'a str;
306 |
307 | // 不合法,无法确定输出的生命周期,因为没有输入的
308 | fn get_str() -> &str;
309 |
310 | // 显式的写法包括
311 | fn get_str<'a>() -> &'a str; // 泛型版本
312 | fn get_str() -> &'static str; // 'static 版本
313 |
314 | // 不合法,无法确定输出的生命周期,因为有多个输入
315 | fn overlap(s: &str, t: &str) -> &str;
316 |
317 | // 显式(但仍有部分省略)的写法包括
318 | fn overlap<'a>(s: &'a str, t: &str) -> &'a str; // 输出生命周期不能长于s
319 | fn overlap<'a>(s: &str, t: &'a str) -> &'a str; // 输出生命周期不能长于t
320 | fn overlap<'a>(s: &'a str, t: &'a str) -> &'a str; // 输出生命周期不能长于s和t
321 | fn overlap(s: &str, t: &str) -> &'static str; // 输出生命周期可以长于s和t
322 | fn overlap<'a>(s: &str, t: &str) -> &'a str; // 输入和输出的生命周期无关
323 |
324 | // 展开
325 | fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'a str;
326 | fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'b str;
327 | fn overlap<'a>(s: &'a str, t: &'a str) -> &'a str;
328 | fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'static str;
329 | fn overlap<'a, 'b, 'c>(s: &'a str, t: &'b str) -> &'c str;
330 |
331 | // 省略
332 | fn compare(&self, s: &str) -> &str;
333 |
334 | // 展开
335 | fn compare<'a, 'b>(&'a self, &'b str) -> &'a str;
336 | ```
337 |
338 | 如果你曾写过
339 | - 结构体方法
340 | - 接收引用的函数
341 | - 返回引用的函数
342 | - 泛型函数
343 | - trait object(后面会有更详细的讨论)
344 | - 闭包(后面会有更详细的讨论)
345 |
346 | 那么你的代码就有被省略的泛型生命周期记号。
347 |
348 | **要点**
349 | - 几乎所有Rust代码都是泛型代码,到处都有被省略的生命周期记号
350 |
351 |
352 | ### 5) 如果编译能通过,那么我的生命周期标注就是正确的
353 |
354 | **误解推论**
355 | - Rust对函数的的生命周期省略规则总是正确的
356 | - Rust的借用检查器在技术上和语义上总是正确的
357 | - Rust比我更了解我的程序的语义
358 |
359 |
360 | Rust程序是有可能在技术上能通过编译,但语义上仍然是错的。来看一下这个例子:
361 |
362 |
363 | ```rust
364 | struct ByteIter<'a> {
365 | remainder: &'a [u8]
366 | }
367 |
368 | impl<'a> ByteIter<'a> {
369 | fn next(&mut self) -> Option<&u8> {
370 | if self.remainder.is_empty() {
371 | None
372 | } else {
373 | let byte = &self.remainder[0];
374 | self.remainder = &self.remainder[1..];
375 | Some(byte)
376 | }
377 | }
378 | }
379 |
380 | fn main() {
381 | let mut bytes = ByteIter { remainder: b"1" };
382 | assert_eq!(Some(&b'1'), bytes.next());
383 | assert_eq!(None, bytes.next());
384 | }
385 | ```
386 |
387 | `ByteIter` 是在字节切片上迭代的迭代器,为了简洁我们跳过对 `Iterator` trait的实现。
388 | 这看起来没什么问题,但如果我们想同时检查多个字节呢?
389 |
390 | ```rust
391 | fn main() {
392 | let mut bytes = ByteIter { remainder: b"1123" };
393 | let byte_1 = bytes.next();
394 | let byte_2 = bytes.next();
395 | if byte_1 == byte_2 {
396 | // 做点什么
397 | }
398 | }
399 | ```
400 |
401 | 啊哦!编译错误:
402 |
403 | ```rust
404 | error[E0499]: cannot borrow `bytes` as mutable more than once at a time
405 | --> src/main.rs:20:18
406 | |
407 | 19 | let byte_1 = bytes.next();
408 | | ----- first mutable borrow occurs here
409 | 20 | let byte_2 = bytes.next();
410 | | ^^^^^ second mutable borrow occurs here
411 | 21 | if byte_1 == byte_2 {
412 | | ------ first borrow later used here
413 | ```
414 |
415 | 我觉得我们可以拷贝每一个字节。拷贝在我们处理字节的时候是可行的,
416 | 但当我们从 `ByteIter` 转向泛型切片迭代器用来迭代任意 `&'a [T]` 的时候
417 | 我们也会想到将来可能它会被应用到那些拷贝/克隆的代价很昂贵或根本不可能的类型上。
418 | 噢,我想我们对这没什么办法,代码能过编译,那么生命周期标记必然是对的不是吗?
419 |
420 |
421 | ```rust
422 | struct ByteIter<'a> {
423 | remainder: &'a [u8]
424 | }
425 |
426 | impl<'a> ByteIter<'a> {
427 | fn next<'b>(&'b mut self) -> Option<&'b u8> {
428 | if self.remainder.is_empty() {
429 | None
430 | } else {
431 | let byte = &self.remainder[0];
432 | self.remainder = &self.remainder[1..];
433 | Some(byte)
434 | }
435 | }
436 | }
437 | ```
438 |
439 | 这一点帮助都没有,我仍然搞不明白。这里有个只有Rust专家才知道的小窍门:
440 | 给你的生命周期标记取个有描述性的名字。我们再试一次:
441 |
442 | ```rust
443 | struct ByteIter<'remainder> {
444 | remainder: &'remainder [u8]
445 | }
446 |
447 | impl<'remainder> ByteIter<'remainder> {
448 | fn next<'mut_self>(&'mut_self mut self) -> Option<&'mut_self u8> {
449 | if self.remainder.is_empty() {
450 | None
451 | } else {
452 | let byte = &self.remainder[0];
453 | self.remainder = &self.remainder[1..];
454 | Some(byte)
455 | }
456 | }
457 | }
458 | ```
459 |
460 | 每个返回的字节都被用 `'mut_self` 标记了,但这些字节显然是来自于 `'remainder` 的,
461 | 让我们来改一下。
462 |
463 | ```rust
464 | struct ByteIter<'remainder> {
465 | remainder: &'remainder [u8]
466 | }
467 |
468 | impl<'remainder> ByteIter<'remainder> {
469 | fn next(&mut self) -> Option<&'remainder u8> {
470 | if self.remainder.is_empty() {
471 | None
472 | } else {
473 | let byte = &self.remainder[0];
474 | self.remainder = &self.remainder[1..];
475 | Some(byte)
476 | }
477 | }
478 | }
479 |
480 | fn main() {
481 | let mut bytes = ByteIter { remainder: b"1123" };
482 | let byte_1 = bytes.next();
483 | let byte_2 = bytes.next();
484 | std::mem::drop(bytes); // 我们甚至可以在这里把迭代器drop掉!
485 | if byte_1 == byte_2 { // 编译通过
486 | // 做点什么
487 | }
488 | }
489 | ```
490 |
491 | 现在让我们回顾一下,我们前一版的程序显然是错误的,但为什么Rust仍然允许它通过编译呢?
492 | 答案很简单:这么做是内存安全的。
493 |
494 | Rust的借用检查器对程序的生命周期标记只要求到能够以静态的方式验证程序的内存安全。
495 | Rust会爽快地编译一个程序,即使它的生命周期标记有语义上的错误,
496 | 这带来的结果就是程序会变得过于受限。
497 |
498 | 来看一个与前一个相反的例子:Rust的生命周期省略规则恰好在这个例子上语义是正确的,
499 | 但我们却无意中用了一些多余的显式生命周期标记写了个非常受限的方法。
500 |
501 | ```rust
502 | #[derive(Debug)]
503 | struct NumRef<'a>(&'a i32);
504 |
505 | impl<'a> NumRef<'a> {
506 | // 我的结构体是在'a上泛型的,所以我同样也要
507 | // 标记一下我的self参数,对吗?(答案是:不,不对)
508 | fn some_method(&'a mut self) {}
509 | }
510 |
511 | fn main() {
512 | let mut num_ref = NumRef(&5);
513 | num_ref.some_method(); // 可变借用num_ref直到它剩余的生命周期结束
514 | num_ref.some_method(); // 编译错误
515 | println!("{:?}", num_ref); // 同样编译错误
516 | }
517 | ```
518 |
519 | 如果我们有一个在 `'a` 上的泛型,我们几乎永远不会想要写一个接收 `&'a mut self`的方法。
520 | 因为这意味着我们告诉Rust,这个方法会可变借用这个结构体直到整个结构体生命周期结束。
521 | 这也就告诉Rust的借用检查器最多只允许 `some_method` 被调用一次,
522 | 在这之后这个结构体将会被永久性地可变借用走,也就变得不可用了。
523 | 这样的用例非常非常少,但处于困惑中的初学者非常容易写出这种代码,并能通过编译。
524 | 正确的做法是不要添加这些多余的显式生命周期标记,让Rust的生命周期省略规则来处理它:
525 |
526 |
527 | ```rust
528 | #[derive(Debug)]
529 | struct NumRef<'a>(&'a i32);
530 |
531 | impl<'a> NumRef<'a> {
532 | // 去掉mut self前面的'a
533 | fn some_method(&mut self) {}
534 |
535 | // 上一段代码脱掉语法糖后变为
536 | fn some_method_desugared<'b>(&'b mut self){}
537 | }
538 |
539 | fn main() {
540 | let mut num_ref = NumRef(&5);
541 | num_ref.some_method();
542 | num_ref.some_method(); // 编译通过
543 | println!("{:?}", num_ref); // 编译通过
544 | }
545 | ```
546 |
547 | **要点**
548 | - Rust的函数生命周期省略规则并不总是对所有情况都正确的
549 | - Rust对你的程序的语义了解并不比你多
550 | - 给你的生命周期标记起一个更有描述性的名字
551 | - 在你使用显式生命周期标记的时候要想清楚它们应该被用在哪以及为什么要这么用
552 |
553 |
554 | ### 6) 装箱的trait对象没有生命周期
555 |
556 | 早前我们讨论了Rust对函数的生命周期省略规则。Rust同样有着对于trait对象的生命周期省略规则,它们是:
557 | - 如果一个trait对象作为一个类型参数传递到泛型中,那么它的生命约束会从它包含的类型中推断
558 | - 如果包含的类型中有唯一的约束,那么就使用这个约束。
559 | - 如果包含的类型中有超过一个约束,那么必须显式指定约束。
560 | - 如果以上都不适用,那么:
561 | - 如果trait是以单个生命周期约束定义的,那么就使用这个约束
562 | - 如果所有生命周期约束都是 `'static` 的,那么就使用 `'static` 作为约束
563 | - 如果trait没有生命周期约束,那么它的生命周期将会从表达式中推断,如果不在表达式中,那么就是 `'static` 的
564 |
565 | 这么多东西听起来超级复杂,但我们可以简单地总结为 _"trait对象的生命周期约束是从上下文中推断出来的。"_
566 | 在我们看过几个例子后,我们会发现生命周期约束推断其实是很符合直觉的,我们不需要去记这些很正式的规则。
567 |
568 | ```rust
569 | use std::cell::Ref;
570 |
571 | trait Trait {}
572 |
573 | // 省略
574 | type T1 = Box;
575 | // 展开,Box对T没有生命周期约束,所以被推断为'static
576 | type T2 = Box;
577 |
578 | // 省略
579 | impl dyn Trait {}
580 | // 展开
581 | impl dyn Trait + 'static {}
582 |
583 | // 省略
584 | type T3<'a> = &'a dyn Trait;
585 | // 展开, 因为&'a T 要求 T: 'a, 所以推断为 'a
586 | type T4<'a> = &'a (dyn Trait + 'a);
587 |
588 | // 省略
589 | type T5<'a> = Ref<'a, dyn Trait>;
590 | // 展开, 因为Ref<'a, T> 要求 T: 'a, 所以推断为 'a
591 | type T6<'a> = Ref<'a, dyn Trait + 'a>;
592 |
593 | trait GenericTrait<'a>: 'a {}
594 |
595 | // 省略
596 | type T7<'a> = Box>;
597 | // 展开
598 | type T8<'a> = Box + 'a>;
599 |
600 | // 省略
601 | impl<'a> dyn GenericTrait<'a> {}
602 | // 展开
603 | impl<'a> dyn GenericTrait<'a> + 'a {}
604 | ```
605 |
606 | 实现了某个trait的具体的类型可以包含引用,因此它们同样拥有生命周期约束,且对应的trait对象也有生命周期约束。
607 | 你也可以直接为引用实现trait,而引用显然有生命周期约束。
608 |
609 | ```rust
610 | trait Trait {}
611 |
612 | struct Struct {}
613 | struct Ref<'a, T>(&'a T);
614 |
615 | impl Trait for Struct {}
616 | impl Trait for &Struct {} // 直接在引用类型上实现Trait
617 | impl<'a, T> Trait for Ref<'a, T> {} // 在包含引用的类型上实现Trait
618 | ```
619 |
620 | 不管怎样,这都值得我们仔细研究,因为新手们经常在将一个使用trait对象的函数重构成使用泛型的函数(或者反过来)的时候感到困惑。
621 | 我们来看看这个例子:
622 |
623 | ```rust
624 | use std::fmt::Display;
625 |
626 | fn dynamic_thread_print(t: Box) {
627 | std::thread::spawn(move || {
628 | println!("{}", t);
629 | }).join();
630 | }
631 |
632 | fn static_thread_print(t: T) {
633 | std::thread::spawn(move || {
634 | println!("{}", t);
635 | }).join();
636 | }
637 | ```
638 |
639 | 这会抛出下面的编译错误:
640 |
641 | ```rust
642 | error[E0310]: the parameter type `T` may not live long enough
643 | --> src/lib.rs:10:5
644 | |
645 | 9 | fn static_thread_print(t: T) {
646 | | -- help: consider adding an explicit lifetime bound...: `T: 'static +`
647 | 10 | std::thread::spawn(move || {
648 | | ^^^^^^^^^^^^^^^^^^
649 | |
650 | note: ...so that the type `[closure@src/lib.rs:10:24: 12:6 t:T]` will meet its required lifetime bounds
651 | --> src/lib.rs:10:5
652 | |
653 | 10 | std::thread::spawn(move || {
654 | | ^^^^^^^^^^^^^^^^^^
655 | ```
656 |
657 |
658 | 很好,编译器告诉了我们怎么解决这个问题,我们来试试。
659 |
660 | ```rust
661 | use std::fmt::Display;
662 |
663 | fn dynamic_thread_print(t: Box) {
664 | std::thread::spawn(move || {
665 | println!("{}", t);
666 | }).join();
667 | }
668 |
669 | fn static_thread_print(t: T) {
670 | std::thread::spawn(move || {
671 | println!("{}", t);
672 | }).join();
673 | }
674 | ```
675 |
676 | 编译通过,但这两个函数放在一块儿看起来有点怪,为什么第二个函数对 `T` 有 `'static` 约束,而第一个没有?
677 | 这个问题很刁钻。根据生命周期省略规则,Rust自动为第一个函数推断出 `'static` 约束,所以两个函数实际上都有 `'static` 约束。
678 | 在Rust编译器的眼中是这样的:
679 |
680 | ```rust
681 | use std::fmt::Display;
682 |
683 | fn dynamic_thread_print(t: Box) {
684 | std::thread::spawn(move || {
685 | println!("{}", t);
686 | }).join();
687 | }
688 |
689 | fn static_thread_print(t: T) {
690 | std::thread::spawn(move || {
691 | println!("{}", t);
692 | }).join();
693 | }
694 | ```
695 |
696 | **要点**
697 | - 所有trait对象都有着默认推断的生命周期约束
698 |
699 |
700 |
701 | ### 7) 编译器报错信息会告诉我怎么修改我的代码
702 |
703 | **误解推论**
704 | - Rust编译器对于trait objects的生命周期省略规则总是对的
705 | - Rust编译器比我更懂我代码的语义
706 |
707 | 这个误解是前两个误解的合二为一的例子:
708 |
709 | ```rust
710 | use std::fmt::Display;
711 |
712 | fn box_displayable(t: T) -> Box {
713 | Box::new(t)
714 | }
715 | ```
716 |
717 | 抛出如下错误:
718 |
719 | ```rust
720 | error[E0310]: the parameter type `T` may not live long enough
721 | --> src/lib.rs:4:5
722 | |
723 | 3 | fn box_displayable(t: T) -> Box {
724 | | -- help: consider adding an explicit lifetime bound...: `T: 'static +`
725 | 4 | Box::new(t)
726 | | ^^^^^^^^^^^
727 | |
728 | note: ...so that the type `T` will meet its required lifetime bounds
729 | --> src/lib.rs:4:5
730 | |
731 | 4 | Box::new(t)
732 | | ^^^^^^^^^^^
733 | ```
734 |
735 | 好吧,让我们照着编译器告诉我们的方式修改它,别在意这种改法基于了一个没有告知的事实:
736 | 编译器自动为我们的boxed trait object推断了一个`'static`的生命周期约束。
737 |
738 | ```rust
739 | use std::fmt::Display;
740 |
741 | fn box_displayable(t: T) -> Box {
742 | Box::new(t)
743 | }
744 | ```
745 |
746 | 现在编译通过了,但这真的是我们想要的吗?可能是,也可能不是,编译去并没有告诉我们其它解决方法
747 | 但这个也许合适。
748 |
749 | ```rust
750 | use std::fmt::Display;
751 |
752 | fn box_displayable<'a, T: Display + 'a>(t: T) -> Box {
753 | Box::new(t)
754 | }
755 | ```
756 |
757 | 这个函数接收的参数和前一个版本一样,但多了不少东西。这样写能让它更好吗?不一定,
758 | 这取决于我们的程序的要求和约束。这个例子有些抽象,让我们来看看更简单明了的情况。
759 |
760 | ```rust
761 | fn return_first(a: &str, b: &str) -> &str {
762 | a
763 | }
764 | ```
765 |
766 | 报错:
767 |
768 | ```rust
769 | error[E0106]: missing lifetime specifier
770 | --> src/lib.rs:1:38
771 | |
772 | 1 | fn return_first(a: &str, b: &str) -> &str {
773 | | ---- ---- ^ expected named lifetime parameter
774 | |
775 | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
776 | help: consider introducing a named lifetime parameter
777 | |
778 | 1 | fn return_first<'a>(a: &'a str, b: &'a str) -> &'a str {
779 | | ^^^^ ^^^^^^^ ^^^^^^^ ^^^
780 | ```
781 |
782 | 这个错误信息建议我们给输入和输出打上相同的生命周期标记。
783 | 这么做虽然能使得编译通过,但却过度限制了返回类型。
784 | 我们真正想要的是这个:
785 |
786 | ```rust
787 | fn return_first<'a>(a: &'a str, b: &str) -> &'a str {
788 | a
789 | }
790 | ```
791 |
792 | **要点**
793 | - Rust对trait object的生命周期省略规则并不是在所有情况下都正确。
794 | - Rust不见得比你更懂你代码的语义。
795 | - Rust编译错误信息给出的修改建议可能能让你的代码编译通过,但这不一定是最符合你的要求的。
796 |
797 |
798 | ### 8) 生命周期可以在运行时变长缩短
799 |
800 | **误解推论**
801 | - 容器类型可以通过更换引用在运行时更改自己的生命周期
802 | - Rust的借用检查会进行深入的控制流分析
803 |
804 | 这过不了编译:
805 |
806 | ```rust
807 | struct Has<'lifetime> {
808 | lifetime: &'lifetime str,
809 | }
810 |
811 | fn main() {
812 | let long = String::from("long");
813 | let mut has = Has { lifetime: &long };
814 | assert_eq!(has.lifetime, "long");
815 |
816 | {
817 | let short = String::from("short");
818 | // 换成短生命周期
819 | has.lifetime = &short;
820 | assert_eq!(has.lifetime, "short");
821 |
822 | // 换回长生命周期(并不行)
823 | has.lifetime = &long;
824 | assert_eq!(has.lifetime, "long");
825 | // `short`在这里析构
826 | }
827 |
828 | // 编译错误,`short`在析构后仍处于借用状态
829 | assert_eq!(has.lifetime, "long");
830 | }
831 | ```
832 |
833 | 报错:
834 |
835 | ```rust
836 | error[E0597]: `short` does not live long enough
837 | --> src/main.rs:11:24
838 | |
839 | 11 | has.lifetime = &short;
840 | | ^^^^^^ borrowed value does not live long enough
841 | ...
842 | 15 | }
843 | | - `short` dropped here while still borrowed
844 | 16 | assert_eq!(has.lifetime, "long");
845 | | --------------------------------- borrow later used here
846 | ```
847 |
848 | 下面这个代码同样过不了编译,报的错和上面一样。
849 |
850 | ```rust
851 | struct Has<'lifetime> {
852 | lifetime: &'lifetime str,
853 | }
854 |
855 | fn main() {
856 | let long = String::from("long");
857 | let mut has = Has { lifetime: &long };
858 | assert_eq!(has.lifetime, "long");
859 |
860 | // 这个代码块不会被执行
861 | if false {
862 | let short = String::from("short");
863 | // 换成短生命周期
864 | has.lifetime = &short;
865 | assert_eq!(has.lifetime, "short");
866 |
867 | // 换回长生命周期(并不行)
868 | has.lifetime = &long;
869 | assert_eq!(has.lifetime, "long");
870 | // `short`在这里析构
871 | }
872 |
873 | // 仍旧编译错误,`short`在析构后仍处于借用状态
874 | assert_eq!(has.lifetime, "long");
875 | }
876 | ```
877 |
878 | 生命周期只会在编译期被静态验证,并且Rust的借用检查只能做到基本的控制流分析,
879 | 它假设每个`if-else`中的代码块和`match`的每个分支都会被执行,
880 | 并且其中的每一个变量都能被指定一个最短的生命周期。
881 | 一旦变量被指定了一个生命周期,它就一直受到这个生命周期约束。变量的生命周期只能缩短,
882 | 并且所有缩短都会在编译器被确定。
883 |
884 |
885 |
886 | **要点**
887 | - 生命周期是在编译期静态验证的
888 | - 生命周期不能在运行时变长、缩短或者改变
889 | - Rust的借用检查总是会为所有变量指定一个最短可能的生命周期,并且假定所有代码路径都会被执行
890 |
891 |
892 | ### 9) 将可变引用降级为共享引用是安全的
893 |
894 | **误解推论**
895 | - 重新借用一个引用会终止它的生命周期并且开始一个新的
896 |
897 | 你可以向一个接收共享引用的函数传递一个可变引用,因为Rust会隐式将可变引用重新借用为不可变引用:
898 |
899 | ```rust
900 | fn takes_shared_ref(n: &i32) {}
901 |
902 | fn main() {
903 | let mut a = 10;
904 | takes_shared_ref(&mut a); // 编译通过
905 | takes_shared_ref(&*(&mut a)); // 上一行的显式写法
906 | }
907 | ```
908 |
909 | 直觉上这没问题,将一个可变引用重新借用为不可变引用,应该不会有什么害处不是吗?
910 | 然而并非如此,下面的代码过不了编译。
911 |
912 | ```rust
913 | fn main() {
914 | let mut a = 10;
915 | let b: &i32 = &*(&mut a); // 重新借用为不可变
916 | let c: &i32 = &a;
917 | dbg!(b, c); // 编译错误
918 | }
919 | ```
920 |
921 | 报错:
922 |
923 | ```rust
924 | error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
925 | --> src/main.rs:4:19
926 | |
927 | 3 | let b: &i32 = &*(&mut a);
928 | | -------- mutable borrow occurs here
929 | 4 | let c: &i32 = &a;
930 | | ^^ immutable borrow occurs here
931 | 5 | dbg!(b, c);
932 | | - mutable borrow later used here
933 | ```
934 |
935 | 可变借用出现后立即重新借用为不可变引用,然后可变引用自身析构。
936 | 为什么Rust会认为这个不可变的重新借用仍具有可变引用的独占生命周期?
937 | 虽然上面这个例子没什么问题,但允许将可变引用降级为共享引用实际上引入了潜在的内存安全问题。
938 |
939 | ```rust
940 | use std::sync::Mutex;
941 |
942 | struct Struct {
943 | mutex: Mutex
944 | }
945 |
946 | impl Struct {
947 | // 将self的可变引用降级为str的共享引用
948 | fn get_string(&mut self) -> &str {
949 | self.mutex.get_mut().unwrap()
950 | }
951 | fn mutate_string(&self) {
952 | // 如果Rust允许将可变引用降级为共享引用,
953 | // 那么下面这行代码会使得所有从get_string中得到的共享引用失效
954 | *self.mutex.lock().unwrap() = "surprise!".to_owned();
955 | }
956 | }
957 |
958 | fn main() {
959 | let mut s = Struct {
960 | mutex: Mutex::new("string".to_owned())
961 | };
962 | let str_ref = s.get_string(); // 可变引用降级为共享引用
963 | s.mutate_string(); // str_ref失效,变为悬空指针
964 | dbg!(str_ref); // 编译错误,和我们预期的一样
965 | }
966 | ```
967 |
968 | 这里的问题在于,当你将一个可变引用重新借用为共享引用,你会遇到一点麻烦:
969 | 即使可变引用已经析构,重新借用出来的共享引用还是会将可变引用的生命周期延长到和自己一样长。
970 | 这种重新借用出来的共享引用非常难用,因为它不能与其它共享引用共存。
971 | 它有着可变引用和不可变引用的所有缺点,却没有它们各自的优点。
972 | 我认为将可变引用重新借用为共享引用应该被认为是Rust的反模式(anti-pattern)。
973 | 对这种反模式保持警惕很重要,这可以让你在看到下面这样的代码的时候更容易发现它:
974 |
975 | ```rust
976 | // 将T的可变引用降级为共享引用
977 | fn some_function(some_arg: &mut T) -> &T;
978 |
979 | struct Struct;
980 |
981 | impl Struct {
982 | // 将self的可变引用降级为self共享引用
983 | fn some_method(&mut self) -> &self;
984 |
985 | // 将self的可变引用降级为T的共享引用
986 | fn other_method(&mut self) -> &T;
987 | }
988 | ```
989 |
990 | 即使你避免了函数和方法签名中的重新借用,Rust仍然会自动隐式重新借用,
991 | 所以很容易无意中遇到这样的问题:
992 |
993 | ```rust
994 | use std::collections::HashMap;
995 |
996 | type PlayerID = i32;
997 |
998 | #[derive(Debug, Default)]
999 | struct Player {
1000 | score: i32,
1001 | }
1002 |
1003 | fn start_game(player_a: PlayerID, player_b: PlayerID, server: &mut HashMap) {
1004 | // 从服务器中获取player,如果不存在则创建并插入一个新的
1005 | let player_a: &Player = server.entry(player_a).or_default();
1006 | let player_b: &Player = server.entry(player_b).or_default();
1007 |
1008 | // 用player做点什么
1009 | dbg!(player_a, player_b); // 编译错误
1010 | }
1011 | ```
1012 |
1013 | 上面的代码编译失败。因为 `or_default()` 返回一个 `&mut Player`,
1014 | 而我们的显式类型标注 `&Player` 使得这个 `&mut Player` 被隐式重新借用为 `&Player` 。
1015 | 为了通过编译,我们不得不这样写:
1016 |
1017 | ```rust
1018 | use std::collections::HashMap;
1019 |
1020 | type PlayerID = i32;
1021 |
1022 | #[derive(Debug, Default)]
1023 | struct Player {
1024 | score: i32,
1025 | }
1026 |
1027 | fn start_game(player_a: PlayerID, player_b: PlayerID, server: &mut HashMap) {
1028 | // 因为我们不能把它们放在一起用,所以这里把返回的Player可变引用析构掉
1029 | server.entry(player_a).or_default();
1030 | server.entry(player_b).or_default();
1031 |
1032 | // 再次获取这些Player,这次以不可变的方式,避免出现隐式重新借用
1033 | let player_a = server.get(&player_a);
1034 | let player_b = server.get(&player_b);
1035 |
1036 | // 用Player做点什么
1037 | dbg!(player_a, player_b); // 编译通过
1038 | }
1039 | ```
1040 |
1041 | 虽然有点尴尬和笨重,但这也算是为内存安全做出的牺牲。
1042 |
1043 | **要点**
1044 | - 尽量不要把可变引用重新借用为共享引用,不然你会遇到不少麻烦
1045 | - 重新借用一个可变引用不会使得它的生命周期终结,即使这个可变引用已经析构
1046 |
1047 |
1048 |
1049 | ### 10) 闭包遵循和函数相同的生命周期省略规则
1050 |
1051 | 比起误解,这更像是Rust的一个小陷阱。
1052 |
1053 | 闭包,虽然也是个函数,但是它并不遵循和函数相同的生命周期省略规则。
1054 |
1055 | ```rust
1056 | fn function(x: &i32) -> &i32 {
1057 | x
1058 | }
1059 |
1060 | fn main() {
1061 | let closure = |x: &i32| x;
1062 | }
1063 | ```
1064 |
1065 | 报错:
1066 |
1067 | ```rust
1068 | error: lifetime may not live long enough
1069 | --> src/main.rs:6:29
1070 | |
1071 | 6 | let closure = |x: &i32| x;
1072 | | - - ^ returning this value requires that `'1` must outlive `'2`
1073 | | | |
1074 | | | return type of closure is &'2 i32
1075 | | let's call the lifetime of this reference `'1`
1076 | ```
1077 |
1078 | 去掉语法糖后:
1079 |
1080 | ```rust
1081 | // 输入的生命周期应用到输出上
1082 | fn function<'a>(x: &'a i32) -> &'a i32 {
1083 | x
1084 | }
1085 |
1086 | fn main() {
1087 | // 输入和输出有它们自己独有的生命周期
1088 | let closure = for<'a, 'b> |x: &'a i32| -> &'b i32 { x };
1089 | // 注意:上面这行代码不是合法的语法,但可以表达出我们的意思
1090 | }
1091 | ```
1092 |
1093 | 出现这种差异并没有一个好的理由。闭包最早的实现用的类型推断语义和函数不同,
1094 | 现在变得没法改了,因为将它们统一起来会造成一个不兼容的改动。
1095 | 那么我们要怎么样显式标注闭包的类型呢?我们可选的办法有:
1096 |
1097 | ```rust
1098 | fn main() {
1099 | // 转成trait object,变成不定长类型,编译错误
1100 | let identity: dyn Fn(&i32) -> &i32 = |x: &i32| x;
1101 |
1102 | // 可以通过将它分配在堆上来绕过这个错误,但这样很笨重
1103 | let identity: Box &i32> = Box::new(|x: &i32| x);
1104 |
1105 | // 也可以跳过分配,直接创建一个静态的引用
1106 | let identity: &dyn Fn(&i32) -> &i32 = &|x: &i32| x;
1107 |
1108 | // 上一行去掉语法糖之后:)
1109 | let identity: &'static (dyn for<'a> Fn(&'a i32) -> &'a i32 + 'static) = &|x: &i32| -> &i32 { x };
1110 |
1111 | // 理想中的写法是这样的,但这不是有效的语法
1112 | let identity: impl Fn(&i32) -> &i32 = |x: &i32| x;
1113 |
1114 | // 这样也不错,但也不是有效的语法
1115 | let identity = for<'a> |x: &'a i32| -> &'a i32 { x };
1116 |
1117 | // "impl trait"可以写在函数返回的位置,我们也可以这样写
1118 | fn return_identity() -> impl Fn(&i32) -> &i32 {
1119 | |x| x
1120 | }
1121 | let identity = return_identity();
1122 |
1123 | // 前一种解决方案更泛化的写法
1124 | fn annotate(f: F) -> F where F: Fn(&T) -> &T {
1125 | f
1126 | }
1127 | let identity = annotate(|x: &i32| x);
1128 | }
1129 | ```
1130 |
1131 | 相信你已经注意到,在上面的例子中,当闭包类型使用trait约束的时候会遵循一般函数的生命周期省略规则。
1132 |
1133 | 这里没有什么真正的教训和洞察,只是它就是这样的而已。
1134 |
1135 | **要点**
1136 | - 每一门语言都有自己的小陷阱 🤷
1137 |
1138 |
1139 | ### 11) `'static` 引用总能强制转换为 `'a` 引用
1140 |
1141 | 我前面给出了这个例子:
1142 |
1143 | ```rust
1144 | fn get_str<'a>() -> &'a str; // 泛型版本
1145 | fn get_str() -> &'static str; // 'static版本
1146 | ```
1147 |
1148 | 有的读者问我这两个在实践中是否有区别。一开始我也不太确定,但不幸的是,
1149 | 在经过一段时间的研究之后我发现它们在实践中确实存在着区别。
1150 |
1151 | 所以一般来说,在操作值得时候我们可以使用 `'static` 引用来替换 `'a` 引用,
1152 | 因为Rust会自动将 `'static` 引用强制转换到 `'a` 引用。
1153 | 直觉上来讲,这没毛病,在一个要求较短生命周期引用的地方使用一个有着更长的生命周期的引用不会造成内存安全问题。
1154 | 下面的代码和我们想的一样编译通过:
1155 |
1156 | ```rust
1157 | use rand;
1158 |
1159 | fn generic_str_fn<'a>() -> &'a str {
1160 | "str"
1161 | }
1162 |
1163 | fn static_str_fn() -> &'static str {
1164 | "str"
1165 | }
1166 |
1167 | fn a_or_b(a: T, b: T) -> T {
1168 | if rand::random() {
1169 | a
1170 | } else {
1171 | b
1172 | }
1173 | }
1174 |
1175 | fn main() {
1176 | let some_string = "string".to_owned();
1177 | let some_str = &some_string[..];
1178 | let str_ref = a_or_b(some_str, generic_str_fn()); // 编译通过
1179 | let str_ref = a_or_b(some_str, static_str_fn()); // 编译通过
1180 | }
1181 | ```
1182 |
1183 | 然而,这种强制转换并不会在引用作为函数的类型签名的一部分的时候出现,所以下面这段代码无法通过编译:
1184 |
1185 | ```rust
1186 | use rand;
1187 |
1188 | fn generic_str_fn<'a>() -> &'a str {
1189 | "str"
1190 | }
1191 |
1192 | fn static_str_fn() -> &'static str {
1193 | "str"
1194 | }
1195 |
1196 | fn a_or_b_fn(a: T, b_fn: F) -> T
1197 | where F: Fn() -> T
1198 | {
1199 | if rand::random() {
1200 | a
1201 | } else {
1202 | b_fn()
1203 | }
1204 | }
1205 |
1206 | fn main() {
1207 | let some_string = "string".to_owned();
1208 | let some_str = &some_string[..];
1209 | let str_ref = a_or_b_fn(some_str, generic_str_fn); // 编译通过
1210 | let str_ref = a_or_b_fn(some_str, static_str_fn); // 编译错误
1211 | }
1212 | ```
1213 |
1214 | 报错:
1215 |
1216 | ```rust
1217 | error[E0597]: `some_string` does not live long enough
1218 | --> src/main.rs:23:21
1219 | |
1220 | 23 | let some_str = &some_string[..];
1221 | | ^^^^^^^^^^^ borrowed value does not live long enough
1222 | ...
1223 | 25 | let str_ref = a_or_b_fn(some_str, static_str_fn);
1224 | | ---------------------------------- argument requires that `some_string` is borrowed for `'static`
1225 | 26 | }
1226 | | - `some_string` dropped here while still borrowed
1227 | ```
1228 |
1229 | 这算不算Rust的小陷阱还有待商榷,因为这不是 `&'static str` 到 `&'a str` 简单直接的强制转换,
1230 | 而是 `for Fn() -> &'static T` 到 `for<'a, T> Fn() -> &'a T` 这种更复杂的情况。
1231 | 前者是值之间的强制转换,后者是类型之间的强制转换。
1232 |
1233 | **要点**
1234 | - 签名为 `for<'a, T> Fn() -> &'a T` 的函数要比签名为 `for fn() -> &'static T` 的函数更为灵活,并且能用在更多场景下
1235 |
1236 |
1237 | ## 总结
1238 |
1239 | - `T` 是 `&T` 和 `&mut T` 的超集
1240 | - `&T` 和 `&mut T` 是不相交的集合
1241 | - `T: 'static` 应该被读作 _"`T` 受 `'static` 生命周期约束"_
1242 | - 如果 `T: 'static` 那么 `T` 可以是一个有着 `'static` 生命周期的借用类型,或是一个所有权类型
1243 | - 既然 `T: 'static` 包含了所有权类型,那么意味着 `T`
1244 | - 可以在运行时动态分配
1245 | - 不必在整个程序中都是有效的
1246 | - 可以被安全地任意修改
1247 | - 可以在运行时动态析构
1248 | - 可以有不同长度的生命周期
1249 | - `T: 'a` 比 `&'a T` 更泛化、灵活
1250 | - `T: 'a` 接收所有权类型、带引用的所有权类型,以及引用
1251 | - `&'a T` 只接收引用
1252 | - 如果 `T: 'static` 那么 `T: 'a`,因为对于所有 `'a` 都有 `'static` >= `'a`
1253 | - 几乎所有Rust代码都是泛型的,到处都有省略的生命周期
1254 | - Rust的生命周期省略规则并不是在任何情况下都对
1255 | - Rust并不比你更了解你程序的语义
1256 | - 给生命周期标记起一个有描述性的名字
1257 | - 考虑清楚哪里需要显式写出生命周期标记,以及为什么要这么写
1258 | - 所有trait object都有默认推断的生命周期约束
1259 | - Rust的编译错误信息可以让你的代码通过编译,但不一定是最符合你代码要求的
1260 | - 生命周期是在编译期静态验证的
1261 | - 生命周期不会以任何方式在运行时变长缩短
1262 | - Rust的借用检查总会为每个变量选择一个最短可能的生命周期,并且假定每条代码路径都会被执行
1263 | - 尽量避免将可变引用重新借用为不可变引用,不然你会遇到不少麻烦
1264 | - 重新借用一个可变引用不会终止它的生命周期,即使这个可变引用已经析构
1265 | - 每个语言都有自己的小陷阱 🤷
1266 |
1267 |
1268 |
1269 | ## 讨论
1270 |
1271 | 在这些地方讨论这篇文章
1272 | - [learnrust subreddit](https://www.reddit.com/r/learnrust/comments/gmrcrq/common_rust_lifetime_misconceptions/)
1273 | - [official Rust users forum](https://users.rust-lang.org/t/blog-post-common-rust-lifetime-misconceptions/42950)
1274 | - [Twitter](https://twitter.com/pretzelhammer/status/1263505856903163910)
1275 | - [rust subreddit](https://www.reddit.com/r/rust/comments/golrsx/common_rust_lifetime_misconceptions/)
1276 | - [Hackernews](https://news.ycombinator.com/item?id=23279731)
1277 |
1278 |
1279 |
1280 | ## 关注
1281 |
1282 | [在Twitter上关注pretzelhammer](https://twitter.com/pretzelhammer) 来获取最新的博客的更新!
1283 |
1284 | ## 拓展阅读
1285 | [Learning Rust in 2020](./learning-rust-in-2020.md)
1286 |
--------------------------------------------------------------------------------
/posts/common-rust-lifetime-misconceptions.md:
--------------------------------------------------------------------------------
1 | # Common Rust Lifetime Misconceptions
2 |
3 | _May 19th, 2020 · 37 minute read · #rust · #lifetimes_
4 |
5 | **Table of Contents**
6 | - [Intro](#intro)
7 | - [The Misconceptions](#the-misconceptions)
8 | - [1) `T` only contains owned types](#1-t-only-contains-owned-types)
9 | - [2) if `T: 'static` then `T` must be valid for the entire program](#2-if-t-static-then-t-must-be-valid-for-the-entire-program)
10 | - [3) `&'a T` and `T: 'a` are the same thing](#3-a-t-and-t-a-are-the-same-thing)
11 | - [4) my code isn't generic and doesn't have lifetimes](#4-my-code-isnt-generic-and-doesnt-have-lifetimes)
12 | - [5) if it compiles then my lifetime annotations are correct](#5-if-it-compiles-then-my-lifetime-annotations-are-correct)
13 | - [6) boxed trait objects don't have lifetimes](#6-boxed-trait-objects-dont-have-lifetimes)
14 | - [7) compiler error messages will tell me how to fix my program](#7-compiler-error-messages-will-tell-me-how-to-fix-my-program)
15 | - [8) lifetimes can grow and shrink at run-time](#8-lifetimes-can-grow-and-shrink-at-run-time)
16 | - [9) downgrading mut refs to shared refs is safe](#9-downgrading-mut-refs-to-shared-refs-is-safe)
17 | - [10) closures follow the same lifetime elision rules as functions](#10-closures-follow-the-same-lifetime-elision-rules-as-functions)
18 | - [11) `'static` refs can always be coerced into `'a` refs](#11-static-refs-can-always-be-coerced-into-a-refs)
19 | - [Conclusion](#conclusion)
20 | - [Discuss](#discuss)
21 | - [Follow](#follow)
22 | - [Further Reading](#further-reading)
23 |
24 |
25 |
26 | ## Intro
27 |
28 | I've held all of these misconceptions at some point and I see many beginners struggle with these misconceptions today. Some of my terminology might be non-standard, so here's a table of shorthand phrases I use and what I intend for them to mean.
29 |
30 | | Phrase | Shorthand for |
31 | |-|-|
32 | | `T` | 1) a set containing all possible types _or_
2) some type within that set |
33 | | owned type | some non-reference type, e.g. `i32`, `String`, `Vec`, etc |
34 | | 1) borrowed type _or_
2) ref type | some reference type regardless of mutability, e.g. `&i32`, `&mut i32`, etc |
35 | | 1) mut ref _or_
2) exclusive ref | exclusive mutable reference, i.e. `&mut T` |
36 | | 1) immut ref _or_
2) shared ref | shared immutable reference, i.e. `&T` |
37 |
38 |
39 |
40 | ## The Misconceptions
41 |
42 | In a nutshell: A variable's lifetime is how long the data it points to can be statically verified by the compiler to be valid at its current memory address. I'll now spend the next ~6500 words going into more detail about where people commonly get confused.
43 |
44 |
45 |
46 | ### 1) `T` only contains owned types
47 |
48 | This misconception is more about generics than lifetimes but generics and lifetimes are tightly intertwined in Rust so it's not possible to talk about one without also talking about the other. Anyway:
49 |
50 | When I first started learning Rust I understood that `i32`, `&i32`, and `&mut i32` are different types. I also understood that some generic type variable `T` represents a set which contains all possible types. However, despite understanding both of these things separately, I wasn't able to understand them together. In my newbie Rust mind this is how I thought generics worked:
51 |
52 | | | | | |
53 | |-|-|-|-|
54 | | **Type Variable** | `T` | `&T` | `&mut T` |
55 | | **Examples** | `i32` | `&i32` | `&mut i32` |
56 |
57 | `T` contains all owned types. `&T` contains all immutably borrowed types. `&mut T` contains all mutably borrowed types. `T`, `&T`, and `&mut T` are disjoint finite sets. Nice, simple, clean, easy, intuitive, and completely totally wrong. This is how generics actually work in Rust:
58 |
59 | | | | | |
60 | |-|-|-|-|
61 | | **Type Variable** | `T` | `&T` | `&mut T` |
62 | | **Examples** | `i32`, `&i32`, `&mut i32`, `&&i32`, `&mut &mut i32`, ... | `&i32`, `&&i32`, `&&mut i32`, ... | `&mut i32`, `&mut &mut i32`, `&mut &i32`, ... |
63 |
64 | `T`, `&T`, and `&mut T` are all infinite sets, since it's possible to borrow a type ad-infinitum. `T` is a superset of both `&T` and `&mut T`. `&T` and `&mut T` are disjoint sets. Here's a couple examples which validate these concepts:
65 |
66 | ```rust
67 | trait Trait {}
68 |
69 | impl Trait for T {}
70 |
71 | impl Trait for &T {} // compile error
72 |
73 | impl Trait for &mut T {} // compile error
74 | ```
75 |
76 | The above program doesn't compile as expected:
77 |
78 | ```rust
79 | error[E0119]: conflicting implementations of trait `Trait` for type `&_`:
80 | --> src/lib.rs:5:1
81 | |
82 | 3 | impl Trait for T {}
83 | | ------------------- first implementation here
84 | 4 |
85 | 5 | impl Trait for &T {}
86 | | ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&_`
87 |
88 | error[E0119]: conflicting implementations of trait `Trait` for type `&mut _`:
89 | --> src/lib.rs:7:1
90 | |
91 | 3 | impl Trait for T {}
92 | | ------------------- first implementation here
93 | ...
94 | 7 | impl Trait for &mut T {}
95 | | ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&mut _`
96 | ```
97 |
98 | The compiler doesn't allow us to define an implementation of `Trait` for `&T` and `&mut T` since it would conflict with the implementation of `Trait` for `T` which already includes all of `&T` and `&mut T`. The program below compiles as expected, since `&T` and `&mut T` are disjoint:
99 |
100 | ```rust
101 | trait Trait {}
102 |
103 | impl Trait for &T {} // compiles
104 |
105 | impl Trait for &mut T {} // compiles
106 | ```
107 |
108 | **Key Takeaways**
109 | - `T` is a superset of both `&T` and `&mut T`
110 | - `&T` and `&mut T` are disjoint sets
111 |
112 |
113 | ### 2) if `T: 'static` then `T` must be valid for the entire program
114 |
115 | **Misconception Corollaries**
116 | - `T: 'static` should be read as _"`T` has a `'static` lifetime"_
117 | - `&'static T` and `T: 'static` are the same thing
118 | - if `T: 'static` then `T` must be immutable
119 | - if `T: 'static` then `T` can only be created at compile time
120 |
121 | Most Rust beginners get introduced to the `'static` lifetime for the first time in a code example that looks something like this:
122 |
123 | ```rust
124 | fn main() {
125 | let str_literal: &'static str = "str literal";
126 | }
127 | ```
128 |
129 | They get told that `"str literal"` is hardcoded into the compiled binary and is loaded into read-only memory at run-time so it's immutable and valid for the entire program and that's what makes it `'static`. These concepts are further reinforced by the rules surrounding defining `static` variables using the `static` keyword.
130 |
131 | ```rust
132 | static BYTES: [u8; 3] = [1, 2, 3];
133 | static mut MUT_BYTES: [u8; 3] = [1, 2, 3];
134 |
135 | fn main() {
136 | MUT_BYTES[0] = 99; // compile error, mutating static is unsafe
137 |
138 | unsafe {
139 | MUT_BYTES[0] = 99;
140 | assert_eq!(99, MUT_BYTES[0]);
141 | }
142 | }
143 | ```
144 |
145 | Regarding `static` variables
146 | - they can only be created at compile-time
147 | - they should be immutable, mutating them is unsafe
148 | - they're valid for the entire program
149 |
150 | The `'static` lifetime was probably named after the default lifetime of `static` variables, right? So it makes sense that the `'static` lifetime has to follow all the same rules, right?
151 |
152 | Well yes, but a type _with_ a `'static` lifetime is different from a type _bounded by_ a `'static` lifetime. The latter can be dynamically allocated at run-time, can be safely and freely mutated, can be dropped, and can live for arbitrary durations.
153 |
154 | It's important at this point to distinguish `&'static T` from `T: 'static`.
155 |
156 | `&'static T` is an immutable reference to some `T` that can be safely held indefinitely long, including up until the end of the program. This is only possible if `T` itself is immutable and does not move _after the reference was created_. `T` does not need to be created at compile-time. It's possible to generate random dynamically allocated data at run-time and return `'static` references to it at the cost of leaking memory, e.g.
157 |
158 | ```rust
159 | use rand;
160 |
161 | // generate random 'static str refs at run-time
162 | fn rand_str_generator() -> &'static str {
163 | let rand_string = rand::random::().to_string();
164 | Box::leak(rand_string.into_boxed_str())
165 | }
166 | ```
167 |
168 | `T: 'static` is some `T` that can be safely held indefinitely long, including up until the end of the program. `T: 'static` includes all `&'static T` however it also includes all owned types, like `String`, `Vec`, etc. The owner of some data is guaranteed that data will never get invalidated as long as the owner holds onto it, therefore the owner can safely hold onto the data indefinitely long, including up until the end of the program. `T: 'static` should be read as _"`T` is bounded by a `'static` lifetime"_ not _"`T` has a `'static` lifetime"_. A program to help illustrate these concepts:
169 |
170 | ```rust
171 | use rand;
172 |
173 | fn drop_static(t: T) {
174 | std::mem::drop(t);
175 | }
176 |
177 | fn main() {
178 | let mut strings: Vec = Vec::new();
179 | for _ in 0..10 {
180 | if rand::random() {
181 | // all the strings are randomly generated
182 | // and dynamically allocated at run-time
183 | let string = rand::random::().to_string();
184 | strings.push(string);
185 | }
186 | }
187 |
188 | // strings are owned types so they're bounded by 'static
189 | for mut string in strings {
190 | // all the strings are mutable
191 | string.push_str("a mutation");
192 | // all the strings are droppable
193 | drop_static(string); // compiles
194 | }
195 |
196 | // all the strings have been invalidated before the end of the program
197 | println!("i am the end of the program");
198 | }
199 | ```
200 |
201 | **Key Takeaways**
202 | - `T: 'static` should be read as _"`T` is bounded by a `'static` lifetime"_
203 | - if `T: 'static` then `T` can be a borrowed type with a `'static` lifetime _or_ an owned type
204 | - since `T: 'static` includes owned types that means `T`
205 | - can be dynamically allocated at run-time
206 | - does not have to be valid for the entire program
207 | - can be safely and freely mutated
208 | - can be dynamically dropped at run-time
209 | - can have lifetimes of different durations
210 |
211 |
212 |
213 | ### 3) `&'a T` and `T: 'a` are the same thing
214 |
215 | This misconception is a generalized version of the one above.
216 |
217 | `&'a T` requires and implies `T: 'a` since a reference to `T` of lifetime `'a` cannot be valid for `'a` if `T` itself is not valid for `'a`. For example, the Rust compiler will never allow the construction of the type `&'static Ref<'a, T>` because if `Ref` is only valid for `'a` we can't make a `'static` reference to it.
218 |
219 | `T: 'a` includes all `&'a T` but the reverse is not true.
220 |
221 | ```rust
222 | // only takes ref types bounded by 'a
223 | fn t_ref<'a, T: 'a>(t: &'a T) {}
224 |
225 | // takes any types bounded by 'a
226 | fn t_bound<'a, T: 'a>(t: T) {}
227 |
228 | // owned type which contains a reference
229 | struct Ref<'a, T: 'a>(&'a T);
230 |
231 | fn main() {
232 | let string = String::from("string");
233 |
234 | t_bound(&string); // compiles
235 | t_bound(Ref(&string)); // compiles
236 | t_bound(&Ref(&string)); // compiles
237 |
238 | t_ref(&string); // compiles
239 | t_ref(Ref(&string)); // compile error, expected ref, found struct
240 | t_ref(&Ref(&string)); // compiles
241 |
242 | // string var is bounded by 'static which is bounded by 'a
243 | t_bound(string); // compiles
244 | }
245 | ```
246 |
247 | **Key Takeaways**
248 | - `T: 'a` is more general and more flexible than `&'a T`
249 | - `T: 'a` accepts owned types, owned types which contain references, and references
250 | - `&'a T` only accepts references
251 | - if `T: 'static` then `T: 'a` since `'static` >= `'a` for all `'a`
252 |
253 |
254 |
255 | ### 4) my code isn't generic and doesn't have lifetimes
256 |
257 | **Misconception Corollaries**
258 | - it's possible to avoid using generics and lifetimes
259 |
260 | This comforting misconception is kept alive thanks to Rust's lifetime elision rules, which allow you to omit lifetime annotations in functions because the Rust borrow checker will infer them following these rules:
261 | - every input ref to a function gets a distinct lifetime
262 | - if there's exactly one input lifetime it gets applied to all output refs
263 | - if there's multiple input lifetimes but one of them is `&self` or `&mut self` then the lifetime of `self` is applied to all output refs
264 | - otherwise output lifetimes have to be made explicit
265 |
266 | That's a lot to take in so lets look at some examples:
267 |
268 | ```rust
269 | // elided
270 | fn print(s: &str);
271 |
272 | // expanded
273 | fn print<'a>(s: &'a str);
274 |
275 | // elided
276 | fn trim(s: &str) -> &str;
277 |
278 | // expanded
279 | fn trim<'a>(s: &'a str) -> &'a str;
280 |
281 | // illegal, can't determine output lifetime, no inputs
282 | fn get_str() -> &str;
283 |
284 | // explicit options include
285 | fn get_str<'a>() -> &'a str; // generic version
286 | fn get_str() -> &'static str; // 'static version
287 |
288 | // illegal, can't determine output lifetime, multiple inputs
289 | fn overlap(s: &str, t: &str) -> &str;
290 |
291 | // explicit (but still partially elided) options include
292 | fn overlap<'a>(s: &'a str, t: &str) -> &'a str; // output can't outlive s
293 | fn overlap<'a>(s: &str, t: &'a str) -> &'a str; // output can't outlive t
294 | fn overlap<'a>(s: &'a str, t: &'a str) -> &'a str; // output can't outlive s & t
295 | fn overlap(s: &str, t: &str) -> &'static str; // output can outlive s & t
296 | fn overlap<'a>(s: &str, t: &str) -> &'a str; // no relationship between input & output lifetimes
297 |
298 | // expanded
299 | fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'a str;
300 | fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'b str;
301 | fn overlap<'a>(s: &'a str, t: &'a str) -> &'a str;
302 | fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'static str;
303 | fn overlap<'a, 'b, 'c>(s: &'a str, t: &'b str) -> &'c str;
304 |
305 | // elided
306 | fn compare(&self, s: &str) -> &str;
307 |
308 | // expanded
309 | fn compare<'a, 'b>(&'a self, &'b str) -> &'a str;
310 | ```
311 |
312 | If you've ever written
313 | - a struct method
314 | - a function which takes references
315 | - a function which returns references
316 | - a generic function
317 | - a trait object (more on this later)
318 | - a closure (more on this later)
319 |
320 | then your code has generic elided lifetime annotations all over it.
321 |
322 | **Key Takeaways**
323 | - almost all Rust code is generic code and there's elided lifetime annotations everywhere
324 |
325 |
326 |
327 | ### 5) if it compiles then my lifetime annotations are correct
328 |
329 | **Misconception Corollaries**
330 | - Rust's lifetime elision rules for functions are always right
331 | - Rust's borrow checker is always right, technically _and semantically_
332 | - Rust knows more about the semantics of my program than I do
333 |
334 | It's possible for a Rust program to be technically compilable but still semantically wrong. Take this for example:
335 |
336 | ```rust
337 | struct ByteIter<'a> {
338 | remainder: &'a [u8]
339 | }
340 |
341 | impl<'a> ByteIter<'a> {
342 | fn next(&mut self) -> Option<&u8> {
343 | if self.remainder.is_empty() {
344 | None
345 | } else {
346 | let byte = &self.remainder[0];
347 | self.remainder = &self.remainder[1..];
348 | Some(byte)
349 | }
350 | }
351 | }
352 |
353 | fn main() {
354 | let mut bytes = ByteIter { remainder: b"1" };
355 | assert_eq!(Some(&b'1'), bytes.next());
356 | assert_eq!(None, bytes.next());
357 | }
358 | ```
359 |
360 | `ByteIter` is an iterator that iterates over a slice of bytes. We're skipping the `Iterator` trait implementation for conciseness. It seems to work fine, but what if we want to check a couple bytes at a time?
361 |
362 | ```rust
363 | fn main() {
364 | let mut bytes = ByteIter { remainder: b"1123" };
365 | let byte_1 = bytes.next();
366 | let byte_2 = bytes.next();
367 | if byte_1 == byte_2 {
368 | // do something
369 | }
370 | }
371 | ```
372 |
373 | Uh oh! Compile error:
374 |
375 | ```rust
376 | error[E0499]: cannot borrow `bytes` as mutable more than once at a time
377 | --> src/main.rs:20:18
378 | |
379 | 19 | let byte_1 = bytes.next();
380 | | ----- first mutable borrow occurs here
381 | 20 | let byte_2 = bytes.next();
382 | | ^^^^^ second mutable borrow occurs here
383 | 21 | if byte_1 == byte_2 {
384 | | ------ first borrow later used here
385 | ```
386 |
387 | I guess we can copy each byte. Copying is okay when we're working with bytes but if we turned `ByteIter` into a generic slice iterator that can iterate over any `&'a [T]` then we might want to use it in the future with types that may be very expensive or impossible to copy / clone. Oh well, I guess there's nothing we can do about that, the code compiles so the lifetime annotations must be right, right?
388 |
389 | Nope, the current lifetime annotations are actually the source of the bug! It's particularly hard to spot because the buggy lifetime annotations are elided. Lets expand the elided lifetimes to get a clearer look at the problem:
390 |
391 | ```rust
392 | struct ByteIter<'a> {
393 | remainder: &'a [u8]
394 | }
395 |
396 | impl<'a> ByteIter<'a> {
397 | fn next<'b>(&'b mut self) -> Option<&'b u8> {
398 | if self.remainder.is_empty() {
399 | None
400 | } else {
401 | let byte = &self.remainder[0];
402 | self.remainder = &self.remainder[1..];
403 | Some(byte)
404 | }
405 | }
406 | }
407 | ```
408 |
409 | That didn't help at all. I'm still confused. Here's a hot tip that only Rust pros know: give your lifetime annotations descriptive names. Lets try again:
410 |
411 | ```rust
412 | struct ByteIter<'remainder> {
413 | remainder: &'remainder [u8]
414 | }
415 |
416 | impl<'remainder> ByteIter<'remainder> {
417 | fn next<'mut_self>(&'mut_self mut self) -> Option<&'mut_self u8> {
418 | if self.remainder.is_empty() {
419 | None
420 | } else {
421 | let byte = &self.remainder[0];
422 | self.remainder = &self.remainder[1..];
423 | Some(byte)
424 | }
425 | }
426 | }
427 | ```
428 |
429 | Each returned byte is annotated with `'mut_self` but the bytes are clearly coming from `'remainder`! Lets fix it.
430 |
431 | ```rust
432 | struct ByteIter<'remainder> {
433 | remainder: &'remainder [u8]
434 | }
435 |
436 | impl<'remainder> ByteIter<'remainder> {
437 | fn next(&mut self) -> Option<&'remainder u8> {
438 | if self.remainder.is_empty() {
439 | None
440 | } else {
441 | let byte = &self.remainder[0];
442 | self.remainder = &self.remainder[1..];
443 | Some(byte)
444 | }
445 | }
446 | }
447 |
448 | fn main() {
449 | let mut bytes = ByteIter { remainder: b"1123" };
450 | let byte_1 = bytes.next();
451 | let byte_2 = bytes.next();
452 | std::mem::drop(bytes); // we can even drop the iterator now!
453 | if byte_1 == byte_2 { // compiles
454 | // do something
455 | }
456 | }
457 | ```
458 |
459 | Now that we look back on the previous version of our program it was obviously wrong, so why did Rust compile it? The answer is simple: it was memory safe.
460 |
461 | The Rust borrow checker only cares about the lifetime annotations in a program to the extent it can use them to statically verify the memory safety of the program. Rust will happily compile programs even if the lifetime annotations have semantic errors, and the consequence of this is that the program becomes unnecessarily restrictive.
462 |
463 | Here's a quick example that's the opposite of the previous example: Rust's lifetime elision rules happen to be semantically correct in this instance but we unintentionally write a very restrictive method with our own unnecessary explicit lifetime annotations.
464 |
465 | ```rust
466 | #[derive(Debug)]
467 | struct NumRef<'a>(&'a i32);
468 |
469 | impl<'a> NumRef<'a> {
470 | // my struct is generic over 'a so that means I need to annotate
471 | // my self parameters with 'a too, right? (answer: no, not right)
472 | fn some_method(&'a mut self) {}
473 | }
474 |
475 | fn main() {
476 | let mut num_ref = NumRef(&5);
477 | num_ref.some_method(); // mutably borrows num_ref for the rest of its lifetime
478 | num_ref.some_method(); // compile error
479 | println!("{:?}", num_ref); // also compile error
480 | }
481 | ```
482 |
483 | If we have some struct generic over `'a` we almost never want to write a method with a `&'a mut self` receiver. What we're communicating to Rust is "this method will mutably borrow the struct for the entirety of the struct's lifetime". In practice this means Rust's borrow checker will only allow at most one call to `some_method` before the struct becomes permanently mutably borrowed and thus unusable. The use-cases for this are extremely rare but the code above is very easy for confused beginners to write and it compiles. The fix is to not add unnecessary explicit lifetime annotations and let Rust's lifetime elision rules handle it:
484 |
485 | ```rust
486 | #[derive(Debug)]
487 | struct NumRef<'a>(&'a i32);
488 |
489 | impl<'a> NumRef<'a> {
490 | // no more 'a on mut self
491 | fn some_method(&mut self) {}
492 |
493 | // above line desugars to
494 | fn some_method_desugared<'b>(&'b mut self){}
495 | }
496 |
497 | fn main() {
498 | let mut num_ref = NumRef(&5);
499 | num_ref.some_method();
500 | num_ref.some_method(); // compiles
501 | println!("{:?}", num_ref); // compiles
502 | }
503 | ```
504 |
505 | **Key Takeaways**
506 | - Rust's lifetime elision rules for functions are not always right for every situation
507 | - Rust does not know more about the semantics of your program than you do
508 | - give your lifetime annotations descriptive names
509 | - try to be mindful of where you place explicit lifetime annotations and why
510 |
511 |
512 |
513 | ### 6) boxed trait objects don't have lifetimes
514 |
515 | Earlier we discussed Rust's lifetime elision rules _for functions_. Rust also has lifetime elision rules for trait objects, which are:
516 | - if a trait object is used as a type argument to a generic type then its life bound is inferred from the containing type
517 | - if there's a unique bound from the containing then that's used
518 | - if there's more than one bound from the containing type then an explicit bound must be specified
519 | - if the above doesn't apply then
520 | - if the trait is defined with a single lifetime bound then that bound is used
521 | - if `'static` is used for any lifetime bound then `'static` is used
522 | - if the trait has no lifetime bounds then its lifetime is inferred in expressions and is `'static` outside of expressions
523 |
524 | All of that sounds super complicated but can be simply summarized as _"a trait object's lifetime bound is inferred from context."_ After looking at a handful of examples we'll see the lifetime bound inferences are pretty intuitive so we don't have to memorize the formal rules:
525 |
526 | ```rust
527 | use std::cell::Ref;
528 |
529 | trait Trait {}
530 |
531 | // elided
532 | type T1 = Box;
533 | // expanded, Box has no lifetime bound on T, so inferred as 'static
534 | type T2 = Box;
535 |
536 | // elided
537 | impl dyn Trait {}
538 | // expanded
539 | impl dyn Trait + 'static {}
540 |
541 | // elided
542 | type T3<'a> = &'a dyn Trait;
543 | // expanded, &'a T requires T: 'a, so inferred as 'a
544 | type T4<'a> = &'a (dyn Trait + 'a);
545 |
546 | // elided
547 | type T5<'a> = Ref<'a, dyn Trait>;
548 | // expanded, Ref<'a, T> requires T: 'a, so inferred as 'a
549 | type T6<'a> = Ref<'a, dyn Trait + 'a>;
550 |
551 | trait GenericTrait<'a>: 'a {}
552 |
553 | // elided
554 | type T7<'a> = Box>;
555 | // expanded
556 | type T8<'a> = Box + 'a>;
557 |
558 | // elided
559 | impl<'a> dyn GenericTrait<'a> {}
560 | // expanded
561 | impl<'a> dyn GenericTrait<'a> + 'a {}
562 | ```
563 |
564 | Concrete types which implement traits can have references and thus they also have lifetime bounds, and so their corresponding trait objects have lifetime bounds. Also you can implement traits directly for references which obviously have lifetime bounds:
565 |
566 | ```rust
567 | trait Trait {}
568 |
569 | struct Struct {}
570 | struct Ref<'a, T>(&'a T);
571 |
572 | impl Trait for Struct {}
573 | impl Trait for &Struct {} // impl Trait directly on a ref type
574 | impl<'a, T> Trait for Ref<'a, T> {} // impl Trait on a type containing refs
575 | ```
576 |
577 | Anyway, this is worth going over because it often confuses beginners when they refactor a function from using trait objects to generics or vice versa. Take this program for example:
578 |
579 | ```rust
580 | use std::fmt::Display;
581 |
582 | fn dynamic_thread_print(t: Box) {
583 | std::thread::spawn(move || {
584 | println!("{}", t);
585 | }).join();
586 | }
587 |
588 | fn static_thread_print(t: T) {
589 | std::thread::spawn(move || {
590 | println!("{}", t);
591 | }).join();
592 | }
593 | ```
594 |
595 | It throws this compile error:
596 |
597 | ```rust
598 | error[E0310]: the parameter type `T` may not live long enough
599 | --> src/lib.rs:10:5
600 | |
601 | 9 | fn static_thread_print(t: T) {
602 | | -- help: consider adding an explicit lifetime bound...: `T: 'static +`
603 | 10 | std::thread::spawn(move || {
604 | | ^^^^^^^^^^^^^^^^^^
605 | |
606 | note: ...so that the type `[closure@src/lib.rs:10:24: 12:6 t:T]` will meet its required lifetime bounds
607 | --> src/lib.rs:10:5
608 | |
609 | 10 | std::thread::spawn(move || {
610 | | ^^^^^^^^^^^^^^^^^^
611 | ```
612 |
613 | Okay great, the compiler tells us how to fix the issue so lets fix the issue.
614 |
615 | ```rust
616 | use std::fmt::Display;
617 |
618 | fn dynamic_thread_print(t: Box) {
619 | std::thread::spawn(move || {
620 | println!("{}", t);
621 | }).join();
622 | }
623 |
624 | fn static_thread_print(t: T) {
625 | std::thread::spawn(move || {
626 | println!("{}", t);
627 | }).join();
628 | }
629 | ```
630 |
631 | It compiles now but these two functions look awkward next to each other, why does the second function require a `'static` bound on `T` where the first function doesn't? That's a trick question. Using the lifetime elision rules Rust automatically infers a `'static` bound in the first function so both actually have `'static` bounds. This is what the Rust compiler sees:
632 |
633 | ```rust
634 | use std::fmt::Display;
635 |
636 | fn dynamic_thread_print(t: Box) {
637 | std::thread::spawn(move || {
638 | println!("{}", t);
639 | }).join();
640 | }
641 |
642 | fn static_thread_print(t: T) {
643 | std::thread::spawn(move || {
644 | println!("{}", t);
645 | }).join();
646 | }
647 | ```
648 |
649 | **Key Takeaways**
650 | - all trait objects have some inferred default lifetime bounds
651 |
652 |
653 |
654 | ### 7) compiler error messages will tell me how to fix my program
655 |
656 | **Misconception Corollaries**
657 | - Rust's lifetime elision rules for trait objects are always right
658 | - Rust knows more about the semantics of my program than I do
659 |
660 | This misconception is the previous 2 misconceptions combined into one example:
661 |
662 | ```rust
663 | use std::fmt::Display;
664 |
665 | fn box_displayable(t: T) -> Box {
666 | Box::new(t)
667 | }
668 | ```
669 |
670 | Throws this error:
671 |
672 | ```rust
673 | error[E0310]: the parameter type `T` may not live long enough
674 | --> src/lib.rs:4:5
675 | |
676 | 3 | fn box_displayable(t: T) -> Box {
677 | | -- help: consider adding an explicit lifetime bound...: `T: 'static +`
678 | 4 | Box::new(t)
679 | | ^^^^^^^^^^^
680 | |
681 | note: ...so that the type `T` will meet its required lifetime bounds
682 | --> src/lib.rs:4:5
683 | |
684 | 4 | Box::new(t)
685 | | ^^^^^^^^^^^
686 | ```
687 |
688 | Okay, let's fix it how the compiler is telling us to fix it, nevermind the fact that it's automatically inferring a `'static` lifetime bound for our boxed trait object without telling us and its recommended fix is based on that unstated fact:
689 |
690 | ```rust
691 | use std::fmt::Display;
692 |
693 | fn box_displayable(t: T) -> Box {
694 | Box::new(t)
695 | }
696 | ```
697 |
698 | So the program compiles now... but is this what we actually want? Probably, but maybe not. The compiler didn't mention any other fixes but this would have also been appropriate:
699 |
700 | ```rust
701 | use std::fmt::Display;
702 |
703 | fn box_displayable<'a, T: Display + 'a>(t: T) -> Box {
704 | Box::new(t)
705 | }
706 | ```
707 |
708 | This function accepts all the same arguments as the previous version plus a lot more! Does that make it better? Not necessarily, it depends on the requirements and constraints of our program. This example is a bit abstract so lets take a look at a simpler and more obvious case:
709 |
710 | ```rust
711 | fn return_first(a: &str, b: &str) -> &str {
712 | a
713 | }
714 | ```
715 |
716 | Throws:
717 |
718 | ```rust
719 | error[E0106]: missing lifetime specifier
720 | --> src/lib.rs:1:38
721 | |
722 | 1 | fn return_first(a: &str, b: &str) -> &str {
723 | | ---- ---- ^ expected named lifetime parameter
724 | |
725 | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
726 | help: consider introducing a named lifetime parameter
727 | |
728 | 1 | fn return_first<'a>(a: &'a str, b: &'a str) -> &'a str {
729 | | ^^^^ ^^^^^^^ ^^^^^^^ ^^^
730 | ```
731 |
732 | The error message recommends annotating both inputs and the output with the same lifetime. If we did this our program would compile but this function would overly-constrain the return type. What we actually want is this:
733 |
734 | ```rust
735 | fn return_first<'a>(a: &'a str, b: &str) -> &'a str {
736 | a
737 | }
738 | ```
739 |
740 | **Key Takeaways**
741 | - Rust's lifetime elision rules for trait objects are not always right for every situation
742 | - Rust does not know more about the semantics of your program than you do
743 | - Rust compiler error messages suggest fixes which will make your program compile which is not that same as fixes which will make you program compile _and_ best suit the requirements of your program
744 |
745 |
746 |
747 | ### 8) lifetimes can grow and shrink at run-time
748 |
749 | **Misconception Corollaries**
750 | - container types can swap references at run-time to change their lifetime
751 | - Rust borrow checker does advanced control flow analysis
752 |
753 | This does not compile:
754 |
755 | ```rust
756 | struct Has<'lifetime> {
757 | lifetime: &'lifetime str,
758 | }
759 |
760 | fn main() {
761 | let long = String::from("long");
762 | let mut has = Has { lifetime: &long };
763 | assert_eq!(has.lifetime, "long");
764 |
765 | {
766 | let short = String::from("short");
767 | // "switch" to short lifetime
768 | has.lifetime = &short;
769 | assert_eq!(has.lifetime, "short");
770 |
771 | // "switch back" to long lifetime (but not really)
772 | has.lifetime = &long;
773 | assert_eq!(has.lifetime, "long");
774 | // `short` dropped here
775 | }
776 |
777 | // compile error, `short` still "borrowed" after drop
778 | assert_eq!(has.lifetime, "long");
779 | }
780 | ```
781 |
782 | It throws:
783 |
784 | ```rust
785 | error[E0597]: `short` does not live long enough
786 | --> src/main.rs:11:24
787 | |
788 | 11 | has.lifetime = &short;
789 | | ^^^^^^ borrowed value does not live long enough
790 | ...
791 | 15 | }
792 | | - `short` dropped here while still borrowed
793 | 16 | assert_eq!(has.lifetime, "long");
794 | | --------------------------------- borrow later used here
795 | ```
796 |
797 | This also does not compile, throws the exact same error as above:
798 |
799 | ```rust
800 | struct Has<'lifetime> {
801 | lifetime: &'lifetime str,
802 | }
803 |
804 | fn main() {
805 | let long = String::from("long");
806 | let mut has = Has { lifetime: &long };
807 | assert_eq!(has.lifetime, "long");
808 |
809 | // this block will never run
810 | if false {
811 | let short = String::from("short");
812 | // "switch" to short lifetime
813 | has.lifetime = &short;
814 | assert_eq!(has.lifetime, "short");
815 |
816 | // "switch back" to long lifetime (but not really)
817 | has.lifetime = &long;
818 | assert_eq!(has.lifetime, "long");
819 | // `short` dropped here
820 | }
821 |
822 | // still a compile error, `short` still "borrowed" after drop
823 | assert_eq!(has.lifetime, "long");
824 | }
825 | ```
826 |
827 | Lifetimes have to be statically verified at compile-time and the Rust borrow checker only does very basic control flow analysis, so it assumes every block in an `if-else` statement and every match arm in a `match` statement can be taken and then chooses the shortest possible lifetime for the variable. Once a variable is bounded by a lifetime it is bounded by that lifetime _forever_. The lifetime of a variable can only shrink, and all the shrinkage is determined at compile-time.
828 |
829 | **Key Takeaways**
830 | - lifetimes are statically verified at compile-time
831 | - lifetimes cannot grow or shrink or change in any way at run-time
832 | - Rust borrow checker will always choose the shortest possible lifetime for a variable assuming all code paths can be taken
833 |
834 |
835 |
836 | ### 9) downgrading mut refs to shared refs is safe
837 |
838 | **Misconception Corollaries**
839 | - re-borrowing a reference ends its lifetime and starts a new one
840 |
841 | You can pass a mut ref to a function expecting a shared ref because Rust will implicitly re-borrow the mut ref as immutable:
842 |
843 | ```rust
844 | fn takes_shared_ref(n: &i32) {}
845 |
846 | fn main() {
847 | let mut a = 10;
848 | takes_shared_ref(&mut a); // compiles
849 | takes_shared_ref(&*(&mut a)); // above line desugared
850 | }
851 | ```
852 |
853 | Intuitively this makes sense, since there's no harm in re-borrowing a mut ref as immutable, right? Surprisingly no, as the program below does not compile:
854 |
855 | ```rust
856 | fn main() {
857 | let mut a = 10;
858 | let b: &i32 = &*(&mut a); // re-borrowed as immutable
859 | let c: &i32 = &a;
860 | dbg!(b, c); // compile error
861 | }
862 | ```
863 |
864 | Throws this error:
865 |
866 | ```rust
867 | error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
868 | --> src/main.rs:4:19
869 | |
870 | 3 | let b: &i32 = &*(&mut a);
871 | | -------- mutable borrow occurs here
872 | 4 | let c: &i32 = &a;
873 | | ^^ immutable borrow occurs here
874 | 5 | dbg!(b, c);
875 | | - mutable borrow later used here
876 | ```
877 |
878 | A mutable borrow does occur, but it's immediately and unconditionally re-borrowed as immutable and then dropped. Why is Rust treating the immutable re-borrow as if it still has the mut ref's exclusive lifetime? While there's no issue in the particular example above, allowing the ability to downgrade mut refs to shared refs does indeed introduce potential memory safety issues:
879 |
880 | ```rust
881 | use std::sync::Mutex;
882 |
883 | struct Struct {
884 | mutex: Mutex
885 | }
886 |
887 | impl Struct {
888 | // downgrades mut self to shared str
889 | fn get_string(&mut self) -> &str {
890 | self.mutex.get_mut().unwrap()
891 | }
892 | fn mutate_string(&self) {
893 | // if Rust allowed downgrading mut refs to shared refs
894 | // then the following line would invalidate any shared
895 | // refs returned from the get_string method
896 | *self.mutex.lock().unwrap() = "surprise!".to_owned();
897 | }
898 | }
899 |
900 | fn main() {
901 | let mut s = Struct {
902 | mutex: Mutex::new("string".to_owned())
903 | };
904 | let str_ref = s.get_string(); // mut ref downgraded to shared ref
905 | s.mutate_string(); // str_ref invalidated, now a dangling pointer
906 | dbg!(str_ref); // compile error as expected
907 | }
908 | ```
909 |
910 | The point here is that when you re-borrow a mut ref as a shared ref you don't get that shared ref without a big gotcha: it extends the mut ref's lifetime for the duration of the re-borrow even if the mut ref itself is dropped. Using the re-borrowed shared ref is very difficult because it's immutable but it can't overlap with any other shared refs. The re-borrowed shared ref has all the cons of a mut ref and all the cons of a shared ref and has the pros of neither. I believe re-borrowing a mut ref as a shared ref should be considered a Rust anti-pattern. Being aware of this anti-pattern is important so that you can easily spot it when you see code like this:
911 |
912 | ```rust
913 | // downgrades mut T to shared T
914 | fn some_function(some_arg: &mut T) -> &T;
915 |
916 | struct Struct;
917 |
918 | impl Struct {
919 | // downgrades mut self to shared self
920 | fn some_method(&mut self) -> &self;
921 |
922 | // downgrades mut self to shared T
923 | fn other_method(&mut self) -> &T;
924 | }
925 | ```
926 |
927 | Even if you avoid re-borrows in function and method signatures Rust still does automatic implicit re-borrows so it's easy to bump into this problem without realizing it like so:
928 |
929 | ```rust
930 | use std::collections::HashMap;
931 |
932 | type PlayerID = i32;
933 |
934 | #[derive(Debug, Default)]
935 | struct Player {
936 | score: i32,
937 | }
938 |
939 | fn start_game(player_a: PlayerID, player_b: PlayerID, server: &mut HashMap) {
940 | // get players from server or create & insert new players if they don't yet exist
941 | let player_a: &Player = server.entry(player_a).or_default();
942 | let player_b: &Player = server.entry(player_b).or_default();
943 |
944 | // do something with players
945 | dbg!(player_a, player_b); // compile error
946 | }
947 | ```
948 |
949 | The above fails to compile. `or_default()` returns a `&mut Player` which we're implicitly re-borrowing as `&Player` because of our explicit type annotations. To do what we want we have to:
950 |
951 | ```rust
952 | use std::collections::HashMap;
953 |
954 | type PlayerID = i32;
955 |
956 | #[derive(Debug, Default)]
957 | struct Player {
958 | score: i32,
959 | }
960 |
961 | fn start_game(player_a: PlayerID, player_b: PlayerID, server: &mut HashMap) {
962 | // drop the returned mut Player refs since we can't use them together anyway
963 | server.entry(player_a).or_default();
964 | server.entry(player_b).or_default();
965 |
966 | // fetch the players again, getting them immutably this time, without any implicit re-borrows
967 | let player_a = server.get(&player_a);
968 | let player_b = server.get(&player_b);
969 |
970 | // do something with players
971 | dbg!(player_a, player_b); // compiles
972 | }
973 | ```
974 |
975 | Kinda awkward and clunky but this is the sacrifice we make at the Altar of Memory Safety.
976 |
977 | **Key Takeaways**
978 | - try not to re-borrow mut refs as shared refs, or you're gonna have a bad time
979 | - re-borrowing a mut ref doesn't end its lifetime, even if the ref is dropped
980 |
981 |
982 |
983 | ### 10) closures follow the same lifetime elision rules as functions
984 |
985 | This is more of a Rust Gotcha than a misconception.
986 |
987 | Closures, despite being functions, do not follow the same lifetime elision rules as functions.
988 |
989 | ```rust
990 | fn function(x: &i32) -> &i32 {
991 | x
992 | }
993 |
994 | fn main() {
995 | let closure = |x: &i32| x;
996 | }
997 | ```
998 |
999 | Throws:
1000 |
1001 | ```rust
1002 | error: lifetime may not live long enough
1003 | --> src/main.rs:6:29
1004 | |
1005 | 6 | let closure = |x: &i32| x;
1006 | | - - ^ returning this value requires that `'1` must outlive `'2`
1007 | | | |
1008 | | | return type of closure is &'2 i32
1009 | | let's call the lifetime of this reference `'1`
1010 | ```
1011 |
1012 | After desugaring we get:
1013 |
1014 | ```rust
1015 | // input lifetime gets applied to output
1016 | fn function<'a>(x: &'a i32) -> &'a i32 {
1017 | x
1018 | }
1019 |
1020 | fn main() {
1021 | // input and output each get their own distinct lifetimes
1022 | let closure = for<'a, 'b> |x: &'a i32| -> &'b i32 { x };
1023 | // note: the above line is not valid syntax, but we need it for illustrative purposes
1024 | }
1025 | ```
1026 |
1027 | There's no good reason for this discrepancy. Closures were first implemented with different type inference semantics than functions and now we're stuck with it forever because to unify them at this point would be a breaking change. So how can we explicitly annotate a closure's type? Our options include:
1028 |
1029 | ```rust
1030 | fn main() {
1031 | // cast to trait object, becomes unsized, oops, compile error
1032 | let identity: dyn Fn(&i32) -> &i32 = |x: &i32| x;
1033 |
1034 | // can allocate it on the heap as a workaround but feels clunky
1035 | let identity: Box &i32> = Box::new(|x: &i32| x);
1036 |
1037 | // can skip the allocation and just create a static reference
1038 | let identity: &dyn Fn(&i32) -> &i32 = &|x: &i32| x;
1039 |
1040 | // previous line desugared :)
1041 | let identity: &'static (dyn for<'a> Fn(&'a i32) -> &'a i32 + 'static) = &|x: &i32| -> &i32 { x };
1042 |
1043 | // this would be ideal but it's invalid syntax
1044 | let identity: impl Fn(&i32) -> &i32 = |x: &i32| x;
1045 |
1046 | // this would also be nice but it's also invalid syntax
1047 | let identity = for<'a> |x: &'a i32| -> &'a i32 { x };
1048 |
1049 | // since "impl trait" works in the function return position
1050 | fn return_identity() -> impl Fn(&i32) -> &i32 {
1051 | |x| x
1052 | }
1053 | let identity = return_identity();
1054 |
1055 | // more generic version of the previous solution
1056 | fn annotate(f: F) -> F where F: Fn(&T) -> &T {
1057 | f
1058 | }
1059 | let identity = annotate(|x: &i32| x);
1060 | }
1061 | ```
1062 |
1063 | As I'm sure you've already noticed from the examples above, when closure types are used as trait bounds they do follow the usual function lifetime elision rules.
1064 |
1065 | There's no real lesson or insight to be had here, it just is what it is.
1066 |
1067 | **Key Takeaways**
1068 | - every language has gotchas 🤷
1069 |
1070 |
1071 | ### 11) `'static` refs can always be coerced into `'a` refs
1072 |
1073 | I presented this code example earlier:
1074 |
1075 | ```rust
1076 | fn get_str<'a>() -> &'a str; // generic version
1077 | fn get_str() -> &'static str; // 'static version
1078 | ```
1079 |
1080 | Several readers contacted me to ask if there was a practical difference between the two. At first I wasn't sure but after some investigation it unforuntately turns out that the answer is yes, there is a practical difference between these two functions.
1081 |
1082 | So ordinarily, when working with values, we can use a `'static` ref in place of an `'a` ref because Rust automatically coerces `'static` refs into `'a` refs. Intuitively this makes sense, since using a ref with a long lifetime where only a short lifetime is required will never cause any memory safety issues. The program below compiles as expected:
1083 |
1084 | ```rust
1085 | use rand;
1086 |
1087 | fn generic_str_fn<'a>() -> &'a str {
1088 | "str"
1089 | }
1090 |
1091 | fn static_str_fn() -> &'static str {
1092 | "str"
1093 | }
1094 |
1095 | fn a_or_b(a: T, b: T) -> T {
1096 | if rand::random() {
1097 | a
1098 | } else {
1099 | b
1100 | }
1101 | }
1102 |
1103 | fn main() {
1104 | let some_string = "string".to_owned();
1105 | let some_str = &some_string[..];
1106 | let str_ref = a_or_b(some_str, generic_str_fn()); // compiles
1107 | let str_ref = a_or_b(some_str, static_str_fn()); // compiles
1108 | }
1109 | ```
1110 |
1111 | However this coercion does not take place when the references are part of a function's type signature, so this does not compile:
1112 |
1113 | ```rust
1114 | use rand;
1115 |
1116 | fn generic_str_fn<'a>() -> &'a str {
1117 | "str"
1118 | }
1119 |
1120 | fn static_str_fn() -> &'static str {
1121 | "str"
1122 | }
1123 |
1124 | fn a_or_b_fn(a: T, b_fn: F) -> T
1125 | where F: Fn() -> T
1126 | {
1127 | if rand::random() {
1128 | a
1129 | } else {
1130 | b_fn()
1131 | }
1132 | }
1133 |
1134 | fn main() {
1135 | let some_string = "string".to_owned();
1136 | let some_str = &some_string[..];
1137 | let str_ref = a_or_b_fn(some_str, generic_str_fn); // compiles
1138 | let str_ref = a_or_b_fn(some_str, static_str_fn); // compile error
1139 | }
1140 | ```
1141 |
1142 | Throws this error:
1143 |
1144 | ```rust
1145 | error[E0597]: `some_string` does not live long enough
1146 | --> src/main.rs:23:21
1147 | |
1148 | 23 | let some_str = &some_string[..];
1149 | | ^^^^^^^^^^^ borrowed value does not live long enough
1150 | ...
1151 | 25 | let str_ref = a_or_b_fn(some_str, static_str_fn);
1152 | | ---------------------------------- argument requires that `some_string` is borrowed for `'static`
1153 | 26 | }
1154 | | - `some_string` dropped here while still borrowed
1155 | ```
1156 |
1157 | It's debatable whether or not this is a Rust Gotcha, since it's not a simple straight-forward case of coercing a `&'static str` into a `&'a str` but coercing a `for Fn() -> &'static T` into a `for<'a, T> Fn() -> &'a T`. The former is a coercion between values and the latter is a coercion between types.
1158 |
1159 | **Key Takeaways**
1160 | - functions with `for<'a, T> fn() -> &'a T` signatures are more flexible and work in more scenarios than functions with `for fn() -> &'static T` signatures
1161 |
1162 |
1163 |
1164 | ## Conclusion
1165 |
1166 | - `T` is a superset of both `&T` and `&mut T`
1167 | - `&T` and `&mut T` are disjoint sets
1168 | - `T: 'static` should be read as _"`T` is bounded by a `'static` lifetime"_
1169 | - if `T: 'static` then `T` can be a borrowed type with a `'static` lifetime _or_ an owned type
1170 | - since `T: 'static` includes owned types that means `T`
1171 | - can be dynamically allocated at run-time
1172 | - does not have to be valid for the entire program
1173 | - can be safely and freely mutated
1174 | - can be dynamically dropped at run-time
1175 | - can have lifetimes of different durations
1176 | - `T: 'a` is more general and more flexible than `&'a T`
1177 | - `T: 'a` accepts owned types, owned types which contain references, and references
1178 | - `&'a T` only accepts references
1179 | - if `T: 'static` then `T: 'a` since `'static` >= `'a` for all `'a`
1180 | - almost all Rust code is generic code and there's elided lifetime annotations everywhere
1181 | - Rust's lifetime elision rules are not always right for every situation
1182 | - Rust does not know more about the semantics of your program than you do
1183 | - give your lifetime annotations descriptive names
1184 | - try to be mindful of where you place explicit lifetime annotations and why
1185 | - all trait objects have some inferred default lifetime bounds
1186 | - Rust compiler error messages suggest fixes which will make your program compile which is not that same as fixes which will make you program compile _and_ best suit the requirements of your program
1187 | - lifetimes are statically verified at compile-time
1188 | - lifetimes cannot grow or shrink or change in any way at run-time
1189 | - Rust borrow checker will always choose the shortest possible lifetime for a variable assuming all code paths can be taken
1190 | - try not to re-borrow mut refs as shared refs, or you're gonna have a bad time
1191 | - re-borrowing a mut ref doesn't end its lifetime, even if the ref is dropped
1192 | - every language has gotchas 🤷
1193 | - functions with `for<'a, T> fn() -> &'a T` signatures are more flexible and work in more scenarios than functions with `for fn() -> &'static T` signatures
1194 |
1195 |
1196 |
1197 | ## Discuss
1198 |
1199 | Discuss this article on
1200 | - [learnrust subreddit](https://www.reddit.com/r/learnrust/comments/gmrcrq/common_rust_lifetime_misconceptions/)
1201 | - [official Rust users forum](https://users.rust-lang.org/t/blog-post-common-rust-lifetime-misconceptions/42950)
1202 | - [Twitter](https://twitter.com/pretzelhammer/status/1263505856903163910)
1203 | - [rust subreddit](https://www.reddit.com/r/rust/comments/golrsx/common_rust_lifetime_misconceptions/)
1204 | - [Hackernews](https://news.ycombinator.com/item?id=23279731)
1205 |
1206 |
1207 |
1208 | ## Follow
1209 |
1210 | [Follow pretzelhammer on Twitter](https://twitter.com/pretzelhammer) to get notified of future blog posts!
1211 |
1212 |
1213 |
1214 | ## Further Reading
1215 |
1216 | [Learning Rust in 2020](./learning-rust-in-2020.md)
1217 |
--------------------------------------------------------------------------------
/posts/learning-rust-in-2020.md:
--------------------------------------------------------------------------------
1 | # Learning Rust in 2020
2 |
3 | _May 9th, 2020 · 17 minute read · #rust_
4 |
5 | **Table of Contents**
6 | - [Intro](#intro)
7 | - [TL;DR](#tldr)
8 | - [Practical Rust Resource Reviews](#practical-rust-resource-reviews)
9 | - [HackerRank](#hackerrank)
10 | - [Project Euler](#project-euler)
11 | - [LeetCode](#leetcode)
12 | - [Codewars](#codewars)
13 | - [Advent of Code](#advent-of-code)
14 | - [Rustlings](#rustlings)
15 | - [Exercism](#xxercism)
16 | - [Conclusion](#conclusion)
17 | - [Discuss](#discuss)
18 | - [Follow](#follow)
19 | - [Further Reading](#further-reading)
20 |
21 | ## Intro
22 |
23 | When I started learning Rust I made the mistake of following the advice to read [The Book](https://doc.rust-lang.org/book/title-page.html) first. While it's a great resource, it's pretty overwhelming for a beginner to get told _"If you'd like to learn this programming language the best way to start is to read this 20 chapter book!"_ Most people give up before they even get started when they get advice like this. Nobody ever told someone to read a 20 chapter book just to get started with Javascript or Python. Rust's learning curve is no joke but you gotta give the people what they want, and they want to program, not read about programming. Programming is fun and reading about programming is not as fun.
24 |
25 | The first 10% of this article is gonna be me giving you advice on how to learn Rust in 2020 following a _practical hands-on coding_ approach. This is the good part of the article. You can safely exit after this part (I'll tell you when). The remaining 90% of this article is me ranting about how most online coding challenge sites have poor support for Rust.
26 |
27 |
28 |
29 | ## TL;DR
30 |
31 | If you're a total Rust newbie and want to learn as much as possible in just one day you should read fasterthanlime's excellent [A half-hour to learn Rust](https://fasterthanli.me/blog/2020/a-half-hour-to-learn-rust/) and then checkout the awesome [Rustlings](https://github.com/rust-lang/rustlings) repo and complete the exercises.
32 |
33 | If you're a Rust beginner you should get started on [Exercism's Rust Track](https://exercism.io/tracks/rust). If you get stuck you should ask your friends Google and StackOverflow for help. I recommend taking the time to get comfortable reading and navigating the [Rust Standard Library Docs](https://doc.rust-lang.org/std/) which is amazing and has simple practical examples for how to use everything inside of it. [Rust by Example](https://doc.rust-lang.org/rust-by-example/) is also a really good high-level reference that you can use to quickly learn Rust syntax and features. If you want to gain a deeper understanding of a certain Rust concept only then do I recommend finding the appropriate chapter in [The Book](https://doc.rust-lang.org/book/title-page.html) to read. The best part of completing an exercise on Exercism is that you get access to all the solutions by other members which you can sort by most-starred to see particularly idiomatic or clever solutions. This is a great way to learn!
34 |
35 | At this point you're probably an advanced beginner and can find your own path. If you need more guidance and would like to continue working on small simple programs I recommend doing the exercises from the [Advent of Code 2018 Calendar](https://adventofcode.com/2018). The reason why I specifically recommended the 2018 calendar is because once you're finished with an exercise you can compare your solution to [BurntSushi's Advent of Code 2018 Rust solutions](https://github.com/BurntSushi/advent-of-code). BurntSushi writes really clean, readable, idiomatic Rust code. Reading the code of an experienced Rustacean will teach you as much as the exercises themselves.
36 |
37 | Exit now, the good part of the article is over.
38 |
39 |
40 |
41 | ## Practical Rust Resource Reviews
42 |
43 | _Alternative title: Reviews of Free Online Resources a Rust Beinnger can use to Practice Writing Small Simple Rust Programs_
44 |
45 | Most of these resources weren't specifically created for the purpose of teaching Rust, however they can all be used to learn and practice Rust and many of them explicitly support Rust submissions and provide Rust-specific versions of problems.
46 |
47 | The resources are ordered from worst to best.
48 |
49 |
50 |
51 | ### [HackerRank](https://www.hackerrank.com)
52 |
53 | Rust is a supported language on HackerRank except you aren't allowed to submit Rust solutions to most of the problems on their site. I tried to upload my solution directly and they refused it:
54 |
55 | 
56 |
57 | This is really strange because I was able to browse Rust solutions for the problem above submitted by other HackerRank users, so it's possible to submit a Rust solution somehow. I tried Googling this issue and but Google didn't return any useful results. There's no way for me to evalute HackerRank other than to tell you not to waste your time with it like I did.
58 |
59 |
60 |
61 | ### [Project Euler](https://projecteuler.net/archives)
62 |
63 | When I first started to learn programming back in 2012 I commonly heard _"If you wanna get up to speed quickly in a new programming language solve some Project Euler problems with it!"_ which was okay advice at the time since there were not many other alternatives but in my opinion Project Euler has very little to do with programming. Project Euler problems are more math problems than they are programming problems. Their challenge lies almost entirely in the mathematical reasoning required to reach the solution as the programming required is usually trivial. I would not recommend solving Project Euler problems as a way to learn Rust unless you're very mathematically inclined and have some nostalgia for the site.
64 |
65 |
66 |
67 | ### [LeetCode](https://leetcode.com/problemset/all/)
68 |
69 | Rust is a supported language on LeetCode. For every problem on LeetCode you get a solution template which usually contains a single unimplemented function which you then have to implement and submit in order to solve the problem. For more involved problems the solution template might include a `struct` and an `impl` block with several unimplemented methods. Unfortunately, these solution templates are not created by humans, they are automatically generated, which results in a lot of really awkward and unidiomatic Rust code. Examples:
70 |
71 | | LeetCode generated Rust | Idiomatic Rust |
72 | |-|-|
73 | | tree problems represent links as `Option>>` | `Option>>` is overkill for tree links and `Option>` works just as well and is much easier to work with |
74 | | methods which obviously mutate self still borrow it immutably, e.g. `fn insert(&self, val: i32)` | methods that mutate self need to borrow it mutably, e.g. `fn insert(&mut self, val: i32)` |
75 | | signed 32-bit integers are used for all numbers, even if the problem is undefined for nonnegative integers, e.g. `fn nth_fib(n: i32) -> i32` | problems which are undefined for nonnegative integers should use unsigned integers, e.g. `fn nth_fib(n: u32) -> u32` |
76 | | functions always take ownership of their arguments, even if it's unnecessary, e.g. `fn sum(nums: Vec) -> i32` | if you don't need ownership then borrow `fn sum(nums: &[i32]) -> i32` |
77 | | functions sometimes ignore basic error cases, e.g. for `fn get_max(nums: Vec) -> i32` what `i32` should be returned if `nums` is empty? | if a result might be undefined the return type should be wrapped in an `Option`, e.g. `fn get_max(nums: &[i32]) -> Option` |
78 |
79 | Other LeetCode issues, specific to Rust:
80 | - LeetCode doesn't allow you to pull in 3rd-party dependencies in solutions. Normally I think this is okay for most languages but Rust in particular has a pretty slim standard library which doesn't even include regex support so a lot of the more complex string parsing problems on LeetCode are pointlessly difficult to solve in Rust but have otherwise trivial solutions in other languages which have regex support in their standard libraries.
81 | - None of the problems in the `concurrency` category accept solutions in Rust. What? Fearless concurrency is one of Rust's major selling points!
82 | - After solving a problem you can go to the problem's comments section to see other user's solutions (as many users like to publish their solutions there) but because Rust isn't very popular on LeetCode sometimes you won't find any Rust solutions ;(
83 |
84 | General LeetCode issues:
85 | - LeetCode has a surprising amount of very low quality problems. Problems can be liked and disliked by users but problems are never removed even if they hit very high dislike ratios. I've seen lots of problems with 100+ votes and 80%+ dislike ratios and I don't understand why they are kept on the site.
86 | - Problem difficulty ratings are kinda off. Problems are rated as Easy, Medium, or Hard but there are many Easy problems with lower solve rates than many Hard problems.
87 | - Not all problems accept solutions in all languages, and you can't filter problems by which languages they accept. None of the graph problems on LeetCode accept Rust solutions, for example.
88 | - LeetCode blocks "premium" problems behind a steep monthly paywall but doesn't offer any kind of premium free-trial so there's no telling if the quality is actually any better than the free problems.
89 |
90 | Things LeetCode does right:
91 | - Solutions to problems are tested against a suite of secret unit tests, but if you fail a particular test case they show you the failed case.
92 | - All of the generated Rust code at least follows rustfmt conventions.
93 |
94 |
95 |
96 | ### [Codewars](https://www.codewars.com/join?language=rust)
97 |
98 | Codewars is a misleading name. There's no war going on at Codewars. There's no time limit to solve problems and your solutions aren't judged on their speed of execution or memory useage. You aren't in competition with anyone else. This isn't a bad thing, just worth pointing out.
99 |
100 | Rust is a supported language on Codewars. For every problem on Codewars you get a solution template which usually contains a single unimplemented function which you then have to implement and submit in order to solve the problem. These solution templates are created by humans, including humans who aren't familiar with Rust, so you occasionally get some awkward and unidiomatic Rust. Examples:
101 |
102 | | Codewars' Rust Problems | Idiomatic Rust |
103 | |-|-|
104 | | sometimes don't follow rustfmt conventions, e.g. `fn makeUppercase(s:&str)->String` | always follows rustfmt conventions, e.g. `fn make_uppercase(s: &str) -> String` |
105 | | sometimes takes signed integer arguments for problems that aren't defined for nonnegative integers, e.g. `fn nth_fib(n: i32) -> i32` | if a problem isn't defined for nonnegative integers use unsigned integer arguments, e.g. `fn nth_fib(n: u32) -> u32` |
106 | | sometimes a problem asks you to return `-1` for the null case, e.g. `fn get_index(needle: i32, haystack: &[i32]) -> i32` | if a result can be null the return type should be wrapped in an `Option`, e.g. `fn get_index(needle: i32, haystack: &[i32]) -> Option` |
107 | | sometimes don't take advantage of deref coercion, e.g. `fn do_stuff(s: &String, list: &Vec)` | takes advantage of deref coercion, e.g. `fn do_stuff(s: &str, list: &[i32])` |
108 |
109 | All of the issues above only happen sometimes since there are Rustaceans of various skill-levels on Codewars translating problems to Rust. This is a huge step up from LeetCode where all of the generated Rust problem code is consistently unidiomatic. However, the Rust community on Codewars as a whole might lean towards the inexperienced side since I've seen some highly upvoted "idiomatic" solutions that were also a bit on the awkward side. Examples:
110 |
111 | | Codewars' highest upvoted Rust solutions | Idiomatic Rust |
112 | |-|-|
113 | | sometimes use an explicit return at the end of a function block, e.g. `return result;` | blocks are evaluated as expressions and implicitly return their last item, an explicit return at the end of a function block is unnecessary, e.g. `result` |
114 | | often use compact formatting to make the solution look more concise | should follow rustfmt conventions |
115 | | sometimes make unnecessary allocations, e.g. `str_slice.to_string().chars()` | if you don't need to allocate then don't, e.g. `str_slice.chars()` |
116 | | often try to solve the problem using nothing but iterators at the cost of everything else | iterators are expressive and idiomatic, but if you have to chain 15 of them in a row and there are multiple levels of nested iterators in-between then perhaps you should consider refactoring to use some helper functions, intermediate variables, and maybe even a for-loop |
117 |
118 | Again, the issues above only happen sometimes. An experienced Rustacean can spot them easily but there are a lot of Rust newbies on these sites who have no clue they are learning anti-patterns.
119 |
120 | Other Codewars issues, specific to Rust:
121 | - Rust doesn't seem that popular on Codewars, the site has 9000 exercises but only 300 of them have been translated to Rust ;(
122 |
123 | Other general Codewars issues:
124 | - Your solution is tested against a suite of secret unit tests, if you fail one of the secret unit tests you aren't shown the failed test case. This is especially annoying if the test case tests for an edge case that wasn't clearly communicated in the problem description.
125 |
126 | Things Codewars does right:
127 | - There's a small whitelist of 3rd-party dependencies you can use to help solve problems with Rust. This whitelist includes: rand, chrono, regex, serde, itertools, and lazy_static which helps round out Rust's standard library and puts it more on par with other languages.
128 | - You can filter problems by language.
129 | - Submitting a solution to a problem also automatically publishes the solution. You can view and upvote other members' solutions. You can sort solutions by most upvotes to see particularly concise and clever solutions, which sometimes will also be very idiomatic (but sometimes not, as explained above).
130 | - Problem difficulty grading is pretty good! Instead of grading problems as Easy, Medium, or Hard like LeetCode, Codewars chooses to grade problems from easiest to hardest as: 8 kyu, 7 kyu, 6 kyu, 5 kyu, 4 kyu, 3 kyu, 2 kyu, 1 kyu. I completed 60 problems in the 8 kyu - 4 kyu range and every level felt a little more difficult than the last, which aligned with my expectations.
131 |
132 |
133 |
134 | ### [Advent of Code](https://adventofcode.com/)
135 |
136 | Advent of Code is totally language-agnostic. This would seem like a minus at first but seeing how horribly HackerRank, LeetCode, and Codewars handle their support for Rust on their sites it's actually a plus. Advent of Code also gets placed above the previously mentioned sites because AoC's exercises are really interesting, diverse, and high quality in my opinion.
137 |
138 | General AoC issues:
139 | - After you finish an exercise there's no way to see other people's Rust solutions unless you search from them on Google, and even after you find some there's no telling how good or idiomatic they are.
140 |
141 | To solve the above issue I recommend going through the 2018 Calendar problems and comparing your solutions to [BurntSushi's AoC 2018 Rust solutions](https://github.com/BurntSushi/advent-of-code). BurntSushi writes really clean, readable, idiomatic Rust code. If you want to go through the 2019 Calendar then I recommend comparing your solutions to [bcmyers' AoC 2019 Rust solutions](https://github.com/bcmyers/aoc2019). The reason I specifically suggest bcmyers' is because he made a [youtube playlist of him coding up the solutions](https://www.youtube.com/playlist?list=PLQXBtq4j4Ozkx3r4eoMstdkkOG98qpBfg) and he does a great job of explaining his thought process and why he's doing what he's doing while he's coding.
142 |
143 | Things AoC got right:
144 | - High quality, interesting, curated exercises that are tied together with a narrative.
145 | - Language agnostic, so while it doesn't teach you any Rust patterns it at least doesn't teach you any Rust anti-patterns either.
146 |
147 |
148 |
149 | ### [Rustlings](https://github.com/rust-lang/rustlings)
150 |
151 | Rustlings is sooo good. All Rustlings exercises are hand-crafted for Rust with love and it's a wonderful breath of fresh air. Finally, a set of exercises that really teach you idiomatic Rust!
152 |
153 | If you're a total Rust newbie you should absolutely checkout [Rustlings](https://github.com/rust-lang/rustlings) and get started on the exercises. I highly recommend reading fasterthanlime's [A half-hour to learn Rust](https://fasterthanli.me/blog/2020/a-half-hour-to-learn-rust/) first as it'll get you up to speed on a lot of Rust syntax and concepts super quickly.
154 |
155 | I have only 1 tiny Rustlings criticism: there are some sudden difficulty spikes in the "error-handling" and "conversions" exercises that I could see some users getting overwhelmed by. I assume most probably make it through, or at least I hope.
156 |
157 | I also have 1 tiny non-criticism: it's too short. This is a non-criticism because it's one of Rustlings design goals to be a quick and gentle introduction to Rust but it's so good that of course I wish it was somehow longer.
158 |
159 |
160 |
161 | ### [Exercism](https://exercism.io/tracks/rust)
162 |
163 | Exercism has a Rust track, which is a collection of exercises roughly ordered by subject and difficulty. The Rust track shares a lot of exercises in common with other tracks, but all of the exercises were translated to Rust by experienced Rustaceans and don't suffer from any of the awkward unidiomatic Rust issues that are common on LeetCode and Codewars. There are about a dozen Rust-specific problems that require you to implement a standard library trait, or write a macro, or write a parallel solution using multiple threads, or write unsafe Rust code. These exercises are by far the highlights of the track and I wish there were more of them. Exercism is second only to Rustlings as a resource for learning Rust. The only reason I placed it above Rustlings is Rustlings can be completed in an evening and Exercism's Rust track will take at least a month to complete so it just has a lot more content.
164 |
165 | Exercism issues, specific to the Rust track:
166 | - "Mentored mode" is useless, as most of the Rust mentors on the site are inactive, and the students heavily outnumber them, so it's much better to go through a track in "practice mode".
167 | - There are 92 exercises but a good chunk of them don't really teach you anything new so they kinda feel like busywork. They could probably cut ~20 exercises from the track to make it feel a lot tighter.
168 |
169 | Things Exercism does right:
170 | - All problems are translated to Rust or written for Rust by experienced Rustaceans.
171 | - There are problems which specifically teach Rust's idioms, design patterns, and unique features.
172 | - Problem difficulties are fairly graded, easy problems are easy, medium problems are medium, hard problems are hard.
173 | - You can include whatever 3rd-party dependencies that you want in your solutions.
174 | - All unit tests are public, if you're failing a test you know exactly why.
175 | - After you submit a solution you can browse other user's solutions, and you can sort solutions by which received the most stars.
176 |
177 |
178 |
179 | ## Conclusion
180 |
181 | Same as the [TL;DR](#tldr) :)
182 |
183 |
184 |
185 | ## Discuss
186 |
187 | Discuss this article on
188 | - [learnrust subreddit](https://www.reddit.com/r/learnrust/comments/ggj8tf/learning_rust_in_2020/)
189 | - [official Rust users forum](https://users.rust-lang.org/t/blog-post-learning-rust-in-2020/42373)
190 | - [Twitter](https://twitter.com/pretzelhammer/status/1259897499122360322)
191 | - [rust subreddit](https://www.reddit.com/r/rust/comments/gie64f/learning_rust_in_2020/)
192 | - [Hackernews](https://news.ycombinator.com/item?id=23160975)
193 |
194 |
195 |
196 | ## Follow
197 |
198 | [Follow pretzelhammer on Twitter](https://twitter.com/pretzelhammer) to get notified of future blog posts!
199 |
200 |
201 |
202 | ## Further Reading
203 |
204 | [Common Rust Lifetime Misconceptions](./common-rust-lifetime-misconceptions.md)
205 |
--------------------------------------------------------------------------------
/posts/why-blog.md:
--------------------------------------------------------------------------------
1 | # Why blog?
2 |
3 | _May 2nd, 2020 · 3 minute read · #blogging_
4 |
5 | While learning Rust I was struggling with certain concepts so I tried to organize my thoughts by writing them down in markdown files and well... I soon had several markdown files. I thought _"Maybe I should start a blog?"_ but then I realized _"I probably shouldn't, my posts will never be as good as anything written on Rust by Niko Matsakis, Saoirse Shipwreckt, Huon Wilson, David Tolnay, Alexis Beingessner, Daniel Keep, Carl Fredrik Samson, Aleksey Kladov, Amos Wegner, Pascal Hertleif, Dimitri Sabadie, Daniel Henry-Mantilla, Steve Klabnik, Jake Goulding, or Carol Nichols"_. That list isn't even exhaustive, there's easily dozens of people I left out! There's lots of super smart folk who are already writing about Rust. However, I have one unique advantage that none of those people have: I'm dumb. Super smart people tend to write super smart articles that only other super smart people understand. There's a gap in the market for us dummies, and it's that gap I intend to fill with my writing.
6 |
7 | Also I saw this comic which was pretty encouraging:
8 |
9 | 
10 |
11 | _artist credit: [stuffman](https://stuffman.tumblr.com/)_
12 |
13 | ## Yeah but why is your blog a github repo and not a website?
14 |
15 | I'm a full-stack webdev by trade so I'm _super nitpicky_ when it comes to websites. I try to perfect every aspect from the HTML to the CSS to the JS to the HTTP responses sent from the backend. I'll spend days on optimizing my build & deploy pipeline from 2 seconds to 1 second which ironically is a huge waste of time that I'll never get back and it's something nobody will ever see or appreciate. I get so lost in the technical details that I spend all my energy on making _the perfect website_ and then I'm too burned out to do any writing. I know there are [hundreds of static site generators](https://www.staticgen.com/) that do 90%+ of the technical work for you but I've tried those in the past and wasn't satisfied with them. That's not because they're bad tools, a lot of them are excellent tools, it's just that I'm a craftsman and not happy with something unless I made it with my own two hands. It's a problem, I know.
16 |
17 | I was almost about to give up on my blog idea but that's when I ran across [Frank McSherry's blog](https://github.com/frankmcsherry/blog). Wow, how come I've never thought of doing that? You don't have to build, deploy, or render anything! You push the markdown files to github and people can just read them on github. It's disgustingly simple. I love it. For the first time ever I'm gonna focus on actually writing.
18 |
19 | One small final note: I plan to focus this blog entirely around Rust. Aside from this initial post I don't intend to use this blog to post about personal things.
20 |
21 | ## Follow
22 |
23 | [Follow pretzelhammer on Twitter](https://twitter.com/pretzelhammer) to get notified of future blog posts!
24 |
25 | ## Further Reading
26 |
27 | - [Learning Rust in 2020](./learning-rust-in-2020.md)
28 | - [Common Rust Lifetime Misconceptions](./common-rust-lifetime-misconceptions.md)
29 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # pretzelhammer's Rust blog 🦀
2 |
3 | I write educational content for Rust beginners and Rust advanced beginners.
4 |
5 | My posts are listed below in reverse chronological order.
6 |
7 | | Date | Title |
8 | |-|-|
9 | | 2020-05-19 | [Common Rust Lifetime Misconceptions](./posts/common-rust-lifetime-misconceptions.md) |
10 | | 2020-05-09 | [Learning Rust in 2020](./posts/learning-rust-in-2020.md) |
11 | | 2020-05-02 | [Why blog?](./posts/why-blog.md) |
12 |
13 | ## Notifications
14 |
15 | [Follow pretzelhammer on Twitter](https://twitter.com/pretzelhammer) to get notified when a new blog post gets published!
16 |
17 | ## Feedback
18 |
19 | If you have any feedback please feel welcome to [open an issue](https://github.com/pretzelhammer/rust-blog/issues/new) on this repo. I also accept pull requests for minor fixes like typos and grammar.
20 |
21 | ## Translations
22 |
23 | If you wanna translate a blog post into another language that's awesome! Please feel free to fork this repo and promote your translation however you like.
24 |
25 | ## Licensing
26 |
27 | To be compatible with [Rust](https://github.com/rust-lang/rust), all code examples in this blog are licensed under [Apache License Version 2.0](./license-apache) or [MIT License](./license-mit), at your option.
28 |
--------------------------------------------------------------------------------