-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
452 lines (222 loc) · 577 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>TwoSix的小木屋</title>
<link href="https://twosix.page/atom.xml" rel="self"/>
<link href="https://twosix.page/"/>
<updated>2024-07-04T15:52:28.396Z</updated>
<id>https://twosix.page/</id>
<author>
<name>TwoSix</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>为你的hexo博客添加一个追番列表</title>
<link href="https://twosix.page/2024/06/28/%E4%B8%BA%E4%BD%A0%E7%9A%84hexo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E8%BF%BD%E7%95%AA%E5%88%97%E8%A1%A8/"/>
<id>https://twosix.page/2024/06/28/%E4%B8%BA%E4%BD%A0%E7%9A%84hexo%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E8%BF%BD%E7%95%AA%E5%88%97%E8%A1%A8/</id>
<published>2024-06-28T08:42:06.000Z</published>
<updated>2024-07-04T15:52:28.396Z</updated>
<content type="html"><![CDATA[ <div class="note p-4 mb-4 rounded-small default"> <p>本文基于插件<a class="link" href="https://github.com/HCLonely/hexo-bilibili-bangumi" >hexo-bilibili-bangumi<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>编写,并修改为适配redefine主题的样式,最终结果示例见我的<a href="https://twosix.page/bangumi/">追番列表</a></p> </div><p>主要是闲来无事逛github的时候发现了<a class="link" href="https://github.com/HCLonely/hexo-bilibili-bangumi" >hexo-bilibili-bangumi<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>这么一个插件,可以爬取bili/bangumi的数据并渲染为一个页面展示你的追番列表,整好我前段时间开始有了bangumi记录追番的习惯,所以想着上手用用。</p><h1 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h1><p>正常来说,按照官方的readme操作完就可以上手使用了</p><ol><li><p>安装插件</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install hexo-bilibili-bangumi --save</span><br></pre></td></tr></table></figure></div></li><li><p>在<code>_config.yml</code>配置文件里添加你的配置(以下配置为了确保一次正常运行,与官方示例不同,完整示例见官方):</p><div class="code-container" data-rel="Yaml"><figure class="iseeu highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">bangumi:</span> <span class="comment"># 追番设置</span></span><br><span class="line"> <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">source:</span> <span class="string">bangumi</span></span><br><span class="line"> <span class="attr">bgmInfoSource:</span> <span class="string">'bgmApi'</span></span><br><span class="line"> <span class="attr">path:</span></span><br><span class="line"> <span class="attr">vmid:</span> <span class="string">根据官方readme获取</span></span><br><span class="line"> <span class="attr">title:</span> <span class="string">'追番列表'</span></span><br><span class="line"> <span class="attr">quote:</span> <span class="string">'生命不息,追番不止!'</span></span><br><span class="line"> <span class="attr">show:</span> <span class="number">1</span></span><br><span class="line"> <span class="attr">lazyload:</span> <span class="literal">false</span></span><br><span class="line"> <span class="attr">srcValue:</span> <span class="string">'__image__'</span></span><br><span class="line"> <span class="attr">lazyloadAttrName:</span> <span class="string">'data-src=__image__'</span></span><br><span class="line"> <span class="attr">loading:</span> </span><br><span class="line"> <span class="attr">showMyComment:</span> <span class="literal">false</span></span><br><span class="line"> <span class="attr">pagination:</span> <span class="literal">false</span></span><br><span class="line"> <span class="attr">metaColor:</span></span><br><span class="line"> <span class="attr">color:</span></span><br><span class="line"> <span class="attr">webp:</span></span><br><span class="line"> <span class="attr">progress:</span></span><br><span class="line"> <span class="attr">extraOrder:</span></span><br><span class="line"> <span class="attr">order:</span> <span class="string">latest</span></span><br><span class="line"> <span class="attr">coverMirror:</span></span><br></pre></td></tr></table></figure></div></li><li><p>编译并生成静态文件</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">hexo c</span><br><span class="line">call hexo bangumi -u <span class="comment"># 必须要在hexo g前添加这句,爬取数据</span></span><br><span class="line">call hexo g</span><br><span class="line">call hexo s</span><br></pre></td></tr></table></figure></div></li><li><p>然后你就可以在<code>/bangumis</code>后缀的页面下看见你的追番列表页面啦。(如果修改了path的话,以你实际填写path为主)</p></li></ol> <div class="note-large blue"> <div class="notel-title rounded-t-lg p-3 font-bold text-lg flex flex-row gap-2 items-center"> <p>附言</p> </div> <div class="notel-content"> <p>实际配置过程中,<code>lazyload</code>选项容易和主题冲突,导致图片一直转圈;<code>pagination</code>选项也会有冲突,导致分页异常。因此上面的配置我都改成了默认关闭。</p> </div> </div><h1 id="进阶"><a href="#进阶" class="headerlink" title="进阶"></a>进阶</h1><p>因为默认的样式不太好看,以及其他配置和我当前使用的主题<code>redefine</code>有诸多冲突,因此需要进行一些修改才能正常使用,以下是我做的部分修改分享,也是给自己作一次存档。</p> <div class="note-large default"> <div class="notel-title rounded-t-lg p-3 font-bold text-lg flex flex-row gap-2 items-center"> <i class="notel-icon fa-solid fa-info"></i><p>信息</p> </div> <div class="notel-content"> <p>本人传统后端出身,对前端一概不通,以下修改基本都是靠堆时间慢慢调试+GPT完成,所以改的不好或有其他方案建议的欢迎批评指出(我们GPT真是太强啦)</p> </div> </div><ol><li><p>针对获取的番剧封面太小的问题,修改了<code>lib/templates/bgm-template.ejs</code>文件(因为我只用bgm源所以是这个,bili源有另一个templates文件);以及途中感觉他的布局有些奇怪,my-comments明明是在picture和右边的内容下面,但却归到右边内容的div里,用负数的padding来移到左边…所以布局也改了改</p><p>主要是把img的<code>width</code>从110改成了130px,然后新增一个<code>bangumi-block</code>的div和<code>mycomments</code>纵向排列。</p><div class="code-container" data-rel="Html"><figure class="iseeu highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-item"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-block"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-picture"</span>></span><span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"<%= lazyload ? (loading || "</span><span class="attr">https:</span>//<span class="attr">cdn.jsdelivr.net</span>/<span class="attr">npm</span>/<span class="attr">hexo-bilibili-bangumi</span>@<span class="attr">1.2.0</span>/<span class="attr">lib</span>/<span class="attr">img</span>/<span class="attr">loading.gif</span>") <span class="attr">:</span> (<span class="attr">srcValue</span> === <span class="string">'__loading__'</span> ? (<span class="attr">loading</span> || "<span class="attr">https:</span>//<span class="attr">cdn.jsdelivr.net</span>/<span class="attr">npm</span>/<span class="attr">hexo-bilibili-bangumi</span>@<span class="attr">1.2.0</span>/<span class="attr">lib</span>/<span class="attr">img</span>/<span class="attr">loading.gif</span>") <span class="attr">:</span> `<span class="attr">https:</span>${<span class="attr">item.cover.replace</span>(/^<span class="attr">https:</span>/, '')}`) %></span>" <%- lazyload ? ` data-src="${item.cover}"` : (lazyloadAttrName ? ` ${lazyloadAttrName.split('=')[0]}="${lazyloadAttrName.split('=')[1] === '__loading__' ? (loading || "https://cdn.jsdelivr.net/npm/[email protected]/lib/img/loading.gif") : (lazyloadAttrName.split('=')[1] === '__image__' ? `https:${item.cover.replace(/^https:/, '')}` : (lazyloadAttrName.split('=')[1] || ''))}"` : "") %> referrerPolicy="no-referrer" width="130" style="width:130px;margin:20px auto;" /></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-info"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-title"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span> <span class="attr">target</span>=<span class="string">"_blank"</span> <span class="attr">href</span>=<span class="string">"https://bangumi.tv/subject/<%= item.id %>"</span>></span><%= item.title || "Unknown" %><span class="tag"></<span class="name">a</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-meta"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-items"</span> <%<span class="attr">-</span> <span class="attr">metaColor</span> %></span>></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-item"</span>></span></span><br><span class="line"> <% if(item.totalCount){ %></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-total"</span>></span><%= item.totalCount %><span class="tag"></<span class="name">span</span>></span><span class="tag"><<span class="name">em</span></span></span><br><span class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"bangumi-info-label-em"</span>></span>0<span class="tag"></<span class="name">em</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <% } %></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-item bangumi-type"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-label"</span>></span>类型<span class="tag"></<span class="name">span</span>></span> <span class="tag"><<span class="name">em</span>></span><%= item.type %><span class="tag"></<span class="name">em</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-item bangumi-wish"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-label"</span>></span>想看<span class="tag"></<span class="name">span</span>></span> <span class="tag"><<span class="name">em</span>></span><%= item.wish %><span class="tag"></<span class="name">em</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-item bangumi-doing"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-label"</span>></span>在看<span class="tag"></<span class="name">span</span>></span> <span class="tag"><<span class="name">em</span>></span><%= item.doing || "-" %><span class="tag"></<span class="name">em</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-item bangumi-collect"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-label"</span>></span>已看<span class="tag"></<span class="name">span</span>></span> <span class="tag"><<span class="name">em</span>></span><%= item.collect || "-" %><span class="tag"></<span class="name">em</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-item bangumi-info-item-score"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-info-label"</span>></span>评分<span class="tag"></<span class="name">span</span>></span> <span class="tag"><<span class="name">em</span>></span><%= item.score || "-" %><span class="tag"></<span class="name">em</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-comments"</span> <%<span class="attr">-</span> <span class="attr">color</span> %></span>></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>简介:<%= item.des || "暂无简介" %><span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <% if (showMyComment && item.myComment) { %></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bangumi-my-comments"</span>></span>我的评分:</span><br><span class="line"> <% if (item.myStars) { %></span><br><span class="line"> <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-starstop"</span>></span><span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"bangumi-starlight stars<%= item.myStars %>"</span>></span><span class="tag"></<span class="name">span</span>></span><span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <% } %></span><br><span class="line"> <span class="tag"><<span class="name">br</span>></span></span><br><span class="line"> 我的评价:<%= item.myComment %></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <% } %></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br></pre></td></tr></table></figure></div></li><li><p>因插件为对swup没作兼容,而redefine主题推荐开启swup,开启后会导致无法加载bangumi插件的js脚本,因此对隔壁的<code>lib/templates/bangumi.ejs</code>进行了修改</p><p>主要是将<code><script></code>标签改成了<code><script data-swup-reload-script type="text/javascript"></code></p><div class="code-container" data-rel="Html"><figure class="iseeu highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">···以上省略</span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">data-swup-reload-script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>></span></span><br><span class="line"> ···以下省略</span><br><span class="line"><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure></div></li><li><p>对样式进行美化(自认为的)</p><p>因bangumi插件已经支持针对不同主题的样式表,所以只需要在<code>/src/lib/templates/theme</code>目录下,新增一个<code>redefine.css</code>文件,然后填写自己重新针对该主题设置的样式表即可,以下是我自己修改的样式表:</p><div class="code-container" data-rel="Css"><figure class="iseeu highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.bangumi-tabs</span> {</span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">15px</span>;</span><br><span class="line"> <span class="attribute">margin-top</span>: <span class="number">15px</span>;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#f4f4f4</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">5px</span> <span class="number">5px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">width</span>: fit-content;</span><br><span class="line"> <span class="attribute">margin-left</span>: auto;</span><br><span class="line"> <span class="attribute">margin-right</span>: auto;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-tab</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">text-align</span>: center;</span><br><span class="line"> <span class="attribute">cursor</span>: pointer;</span><br><span class="line"> <span class="attribute">border</span>: none;</span><br><span class="line"> <span class="attribute">background-color</span>: transparent;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#000</span>;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">5px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">8px</span>;</span><br><span class="line"> <span class="attribute">transition</span>: background-color <span class="number">0.3s</span>, color <span class="number">0.3s</span>, box-shadow <span class="number">0.3s</span>;</span><br><span class="line"> <span class="attribute">font-weight</span>: bold;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">a</span><span class="selector-class">.bangumi-tab</span> {</span><br><span class="line"> <span class="attribute">text-decoration</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-active</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#fafafa</span>;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#005080</span>;</span><br><span class="line"> <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">2px</span> <span class="number">4px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.1</span>);</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">8px</span>;</span><br><span class="line"> <span class="attribute">font-weight</span>: bold;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-tab</span><span class="selector-pseudo">:hover</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#e8e6e6</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-item</span> {</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line"> <span class="attribute">clear</span>: both;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">15px</span>;</span><br><span class="line"> <span class="attribute">margin</span>: <span class="number">15px</span>;</span><br><span class="line"> <span class="attribute">height</span>: fit-content;</span><br><span class="line"> <span class="attribute">background-color</span>: transparent;</span><br><span class="line"> <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">4px</span> <span class="number">8px</span> <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0.1</span>);</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#e1e1e1</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="attribute">transition</span>: transform <span class="number">0.3s</span>, box-shadow <span class="number">0.3s</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> screen <span class="keyword">and</span> (<span class="attribute">max-width</span>: <span class="number">600px</span>) {</span><br><span class="line"> <span class="selector-class">.bangumi-item</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-block</span> {</span><br><span class="line"> <span class="attribute">min-height</span>: <span class="number">180px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-picture</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex <span class="meta">!important</span>;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">padding-left</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">130px</span>;</span><br><span class="line"> <span class="attribute">height</span>: auto;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-picture</span> <span class="selector-tag">img</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">object-fit</span>: cover;</span><br><span class="line"> <span class="attribute">box-shadow</span>: <span class="number">0px</span> <span class="number">0px</span> <span class="number">10px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.3</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info</span> {</span><br><span class="line"> <span class="attribute">padding-left</span>: <span class="number">150px</span>;</span><br><span class="line"> <span class="attribute">margin-top</span>: <span class="number">10px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-title</span> {</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">1.2rem</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-title</span> <span class="selector-tag">a</span> {</span><br><span class="line"> <span class="attribute">line-height</span>: <span class="number">1</span>;</span><br><span class="line"> <span class="attribute">text-decoration</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-meta</span> {</span><br><span class="line"> <span class="attribute">font-size</span>:<span class="number">12px</span>;</span><br><span class="line"> <span class="attribute">padding-right</span>:<span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">height</span>:<span class="number">45px</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-comments</span> {</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">0.92rem</span>;</span><br><span class="line"> <span class="attribute">margin-top</span>:<span class="number">12px</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-comments</span>><span class="selector-tag">p</span> {</span><br><span class="line"> <span class="attribute">word-break</span>: break-all;</span><br><span class="line"> <span class="attribute">text-overflow</span>: ellipsis;</span><br><span class="line"> <span class="attribute">overflow</span>: hidden;</span><br><span class="line"></span><br><span class="line"> <span class="attribute">white-space</span>: normal;</span><br><span class="line"> <span class="attribute">display</span>: -webkit-box;</span><br><span class="line"> -webkit-box-orient: vertical;</span><br><span class="line"> -webkit-line-clamp: <span class="number">3</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-pagination</span> {</span><br><span class="line"> <span class="attribute">margin-top</span>: <span class="number">15px</span>;</span><br><span class="line"> <span class="attribute">text-align</span>: center;</span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">10px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-button</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">5px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-button</span><span class="selector-pseudo">:hover</span> {</span><br><span class="line"> <span class="attribute">background</span>: <span class="number">#657b83</span>;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-hide</span> {</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-show</span> {</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info-items</span> {</span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">0.92rem</span>;</span><br><span class="line"> <span class="attribute">color</span>: <span class="number">#005080</span>;</span><br><span class="line"> <span class="attribute">padding-top</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">line-height</span>: <span class="number">1</span>;</span><br><span class="line"> <span class="attribute">float</span>: left;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-item</span><span class="selector-pseudo">:hover</span> {</span><br><span class="line"> <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">6px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0.25</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info-item</span> {</span><br><span class="line"> <span class="attribute">display</span>: inline-block;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">13%</span>;</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">1px</span> solid <span class="number">#005080</span>;</span><br><span class="line"> <span class="attribute">text-align</span>: center;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">34px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info-label</span> {</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">line-height</span>: <span class="number">12px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info-item</span> <span class="selector-tag">em</span> {</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">padding-top</span>: <span class="number">6px</span>;</span><br><span class="line"> <span class="attribute">line-height</span>: <span class="number">17px</span>;</span><br><span class="line"> <span class="attribute">font-style</span>: normal;</span><br><span class="line"> <span class="attribute">font-weight</span>: <span class="number">700</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info-total</span> {</span><br><span class="line"> <span class="attribute">padding-top</span>: <span class="number">11px</span>;</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">line-height</span>: <span class="number">12px</span>;</span><br><span class="line"> <span class="attribute">font-weight</span>: bold;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info-item-score</span> {</span><br><span class="line"> <span class="attribute">border-right</span>: <span class="number">1px</span> solid <span class="number">#0000</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-info-label-em</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> <span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line"> <span class="attribute">visibility</span>: hidden;</span><br><span class="line"> <span class="attribute">line-height</span>: <span class="number">6px</span> <span class="meta">!important</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span> <span class="meta">!important</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>:<span class="number">650px</span>) {</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-coin</span>,</span><br><span class="line"> <span class="selector-class">.bangumi-type</span> {</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-info-item</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">16%</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>:<span class="number">590px</span>) {</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-danmaku</span>,</span><br><span class="line"> <span class="selector-class">.bangumi-wish</span> {</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-info-item</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">19%</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>:<span class="number">520px</span>) {</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-play</span>,</span><br><span class="line"> <span class="selector-class">.bangumi-doing</span> {</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-info-item</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">24%</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>:<span class="number">480px</span>) {</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-follow</span>,</span><br><span class="line"> <span class="selector-class">.bangumi-collect</span> {</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-info-item</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">30%</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>:<span class="number">400px</span>) {</span><br><span class="line"> <span class="selector-class">.bangumi-area</span> {</span><br><span class="line"> <span class="attribute">display</span>: none;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bangumi-info-item</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">45%</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-my-comments</span> {</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">1px</span> dashed <span class="number">#8f8f8f</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">3px</span>;</span><br><span class="line"> <span class="attribute">border-radius</span>: <span class="number">5px</span>;</span><br><span class="line"> <span class="attribute">margin-left</span>: <span class="number">5px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starstop</span> {</span><br><span class="line"> <span class="attribute">background</span>: transparent <span class="built_in">url</span>(<span class="string">https://cdn.jsdelivr.net/npm/[email protected]/lib/img/rate_star_2x.png</span>);</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">background-size</span>: <span class="number">10px</span> <span class="number">19.5px</span>;</span><br><span class="line"> <span class="attribute">background-position</span>: <span class="number">100%</span> <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">background-repeat</span>: repeat-x;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50px</span>;</span><br><span class="line"> <span class="attribute">display</span>: inline-block;</span><br><span class="line"> <span class="attribute">float</span>: none;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span> {</span><br><span class="line"> <span class="attribute">background</span>: transparent <span class="built_in">url</span>(<span class="string">https://cdn.jsdelivr.net/npm/[email protected]/lib/img/rate_star_2x.png</span>);</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">background-size</span>: <span class="number">10px</span> <span class="number">19.5px</span>;</span><br><span class="line"> <span class="attribute">background-position</span>: <span class="number">100%</span> <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">background-repeat</span>: repeat-x;</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">background-position</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars1</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">5px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars2</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">10px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars3</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">15px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars4</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">20px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars5</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">25px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars6</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">30px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars7</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">35px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars8</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">40px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars9</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">45px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bangumi-starlight</span><span class="selector-class">.stars10</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure></div></li></ol><p>但苦于实在不会前端,目前还遗留了一些问题:1. 不知道该怎么获取主题当前是日间还是夜间模式,然后针对夜间模式进行适配,所以夜间模式下会不太好看;2. 其实想把选页的按钮也改成redefine首页那样的模式,但也确实不会修改了,问GPT也只能做到改样式的程度了。</p><hr><p>如果你想使用我修改后的版本,只需要在<code>path/to/your_blot/node_modules/hexo-bilibili-bangumi</code>目录下,完成以下步骤即可</p><ol><li>安装依赖:<code>npm install</code></li><li>作上述同样的修改</li><li>编译:<code>npm run build</code></li></ol><p>然后hexo重新生成以下就完事了。</p>]]></content>
<summary type="html">
<div class="note p-4 mb-4 rounded-small default">
<p>本文基于插件<a class="link" href="https://github.com/HCLonely/hexo-bilibili-bangumi"</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="前端" scheme="https://twosix.page/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>cmake调用windeployqt实现自动打包qt的dll文件</title>
<link href="https://twosix.page/2024/06/27/cmake%E8%B0%83%E7%94%A8windeployqt%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E6%89%93%E5%8C%85qt%E7%9A%84dll%E6%96%87%E4%BB%B6/"/>
<id>https://twosix.page/2024/06/27/cmake%E8%B0%83%E7%94%A8windeployqt%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E6%89%93%E5%8C%85qt%E7%9A%84dll%E6%96%87%E4%BB%B6/</id>
<published>2024-06-27T12:11:05.000Z</published>
<updated>2024-07-04T15:52:28.391Z</updated>
<content type="html"><![CDATA[<p>最近在编写一个Qt项目,发现Qt在windows部署有一个很方便的工具<code>windeployqt.exe</code>,遂研究如何在cmake里调用这个工具,在install时执行,实现全自动化的发布构建。</p><p>代码如下:</p><div class="code-container" data-rel="Cmake"><figure class="iseeu highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 找到qmake的执行路径(Qt5请更换为你实际的Qt版本)</span></span><br><span class="line"><span class="keyword">get_target_property</span>(qmake_exec_filepath Qt5::qmake IMPORTED_LOCATION)</span><br><span class="line"><span class="comment"># 2. 找到Qt的bin文件夹</span></span><br><span class="line"><span class="keyword">get_filename_component</span>(qt_exec_bin_dir <span class="string">"${qmake_exec_filepath}"</span> DIRECTORY)</span><br><span class="line"><span class="comment"># 3. 找到windeployqt的路径</span></span><br><span class="line"><span class="keyword">find_program</span>(windeployqt_exec_filepath windeployqt HINTS <span class="string">"${qt_exec_bin_dir}"</span>)</span><br><span class="line"><span class="comment"># 4. 在控制台执行命令</span></span><br><span class="line"><span class="keyword">add_custom_command</span>(<span class="keyword">TARGET</span> app POST_BUILD</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="string">"${windeployqt_exec_filepath}"</span> <span class="string">"--dir"</span> <span class="string">"${CMAKE_INSTALL_PREFIX}/你的二进制文件存放的文件夹"</span> <span class="string">"$<TARGET_FILE:你的target名>"</span></span><br><span class="line"> COMMENT <span class="string">"Running windeployqt..."</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></div><p>简单来说思路就是:通过库文件找到qmake的路径->通过qmake找到bin的路径->通过bin找到windeployqt的路径->就可以在控制台调用windeployqt执行命令了。</p><p><code>--dir</code>是指定输出的目录,我使用install进行打包,如果不指定的话会在build目录下执行。</p><hr><p>如果没有像qt一样这么方便的工具怎么办?另外附上一段另外写的,查找其他库的dll并复制过来的代码:</p><div class="code-container" data-rel="Cmake"><figure class="iseeu highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 获取到库的bin路径</span></span><br><span class="line"><span class="keyword">set</span>(VTK_DLL_PATH <span class="variable">${VTK_DIR}</span>/../../../bin)</span><br><span class="line"><span class="comment"># 搜索bin下的dll</span></span><br><span class="line"><span class="keyword">file</span>(GLOB VTK_DLLS <span class="variable">${VTK_DLL_PATH}</span>/*.dll)</span><br><span class="line"><span class="comment"># 对所有dll依次install</span></span><br><span class="line"><span class="keyword">foreach</span>(DLL <span class="variable">${VTK_DLLS}</span>)</span><br><span class="line"> <span class="keyword">install</span>(FILES <span class="variable">${DLL}</span> DESTINATION bin)</span><br><span class="line"><span class="keyword">endforeach</span>()</span><br></pre></td></tr></table></figure></div>]]></content>
<summary type="html"><p>最近在编写一个Qt项目,发现Qt在windows部署有一个很方便的工具<code>windeployqt.exe</code>,遂研究如何在cmake里调用这个工具,在install时执行,实现全自动化的发布构建。</p>
<p>代码如下:</p>
<div class="</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="cmake" scheme="https://twosix.page/tags/cmake/"/>
</entry>
<entry>
<title>【Rust 学习记录】14. 进一步认识Cargo及crates.io</title>
<link href="https://twosix.page/2024/01/23/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9114-%E8%BF%9B%E4%B8%80%E6%AD%A5%E8%AE%A4%E8%AF%86Cargo%E5%8F%8Acrates-io/"/>
<id>https://twosix.page/2024/01/23/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9114-%E8%BF%9B%E4%B8%80%E6%AD%A5%E8%AE%A4%E8%AF%86Cargo%E5%8F%8Acrates-io/</id>
<published>2024-01-23T13:55:47.000Z</published>
<updated>2024-07-04T15:52:28.393Z</updated>
<content type="html"><![CDATA[<p>这一章就主要讲讲Cargo这个工具的一些用途,主要是以下几个部分,没有涉猎到的可以在<a class="link" href="https://doc.rust-lang.org/cargo/" >官网<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>查看cargo更全面的介绍:</p><ol><li>build时使用的release profile相关介绍</li><li>怎么将你写的包发布到creates.io上给别人用</li><li>使用工作空间组织项目</li><li>下载安装craetes.io的包</li><li>使用自定义命令来扩展cargo</li></ol><h1 id="release-profile"><a href="#release-profile" class="headerlink" title="release profile"></a>release profile</h1><p>Rust内置了两套release profile,一套就是我们在<code>cargo build</code>时候使用的debug用dev profile,一套就是<code>cargo build --release</code>对应的发布用release profile</p><p>我们可以在toml文件内自定义的修改这两套配置。对应的在[<a class="link" href="http://profile.xxx/" >profile.xxx<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>]字段内,如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[profile.dev]</span><br><span class="line">opt-level = 0</span><br><span class="line"></span><br><span class="line">[profile.release]</span><br><span class="line">opt-level = 3</span><br></pre></td></tr></table></figure></div><p><strong>opt-level</strong>对应的是编译时的优化等级,等级越高,编译耗时越长,但最终可执行文件的运行速度就越快,值只能是0,1,2,3。其中dev配置默认是0,relese配置默认是3。如果有必要,你也可以自行选择修改成1和2</p><h1 id="怎么发布你的包到creates-io"><a href="#怎么发布你的包到creates-io" class="headerlink" title="怎么发布你的包到creates.io"></a>怎么发布你的包到creates.io</h1><h2 id="编写有用的文档注释"><a href="#编写有用的文档注释" class="headerlink" title="编写有用的文档注释"></a>编写有用的文档注释</h2><p>一份好的文档能帮助用户快速理解这个包的具体用途和使用方法,Rust提供了一种可以快<strong>速生成HTML文档</strong>的注释方法(nb,我还以为是docstring,居然可以直接生成文档)</p><p>之前我们都知道双斜杠<code>//</code>可以注释,而我们的文档注释就是<code>///</code>三斜杠,文档注释主要用来介绍使用方法,而不是介绍包的实现细节,例如:</p><p><strong>lib.rs</strong></p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// 将传入的数字加1</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// # Examples</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// ```</span></span><br><span class="line"><span class="comment">/// let arg = 5;</span></span><br><span class="line"><span class="comment">/// let answer = my_crate::add_one(arg);</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// assert_eq!(6, answer);</span></span><br><span class="line"><span class="comment">/// ```</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">add_one</span>(x: <span class="type">i32</span>) <span class="punctuation">-></span> <span class="type">i32</span> {</span><br><span class="line"> x + <span class="number">1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>第一行介绍了函数对应的功能,然后用类似markdown的语法表明了一块Examples区域,最后提供了一片代码块示例。写完之后,我们只需要运行命令:<code>cargo doc</code>就可以自动生成一份HTML文档了,再执行<code>cargo doc --open</code>就可以自动用浏览器打开文档了。</p><p>生成后的样子:<br> <img lazyload src="/images/loading.svg" data-src="/images/image-wgmx.png" alt="img" ></p><p>除了Examples外,还有其他一些可选的文档区域,如:</p><ul><li><strong>Panics</strong>,指出函数可能引发panic的场景。不想触发panic的调用者应当确保自己的代码不会在这些场景下调用该函数。</li><li><strong>Errors</strong>,当函数返回Result作为结果时,这个区域会指出可能出现的错误,以及造成这些错误的具体原因,它可以帮助调用者在编写代码时为不同的错误采取不同的措施。</li><li><strong>Safety</strong>,当函数使用了unsafe关键字(在第19章讨论)时,这个区域会指出当前函数不安全的原因,以及调用者应当确保的使用前提。</li></ul><p>此外,有意思的是,你写的示例内的代码并不只是给用户看的。在你执行<code>cargo test</code>的时候,<strong>cargo会把你文档注释内的代码一并执行测试</strong>,因为没有比文档内的示例代码不能正常执行更让人恶心的了哈哈哈。</p><hr><p>另一种文档注释的形式是<code>//!</code>。这种文档注释会显示在注释的最外层,一般用在包的根文件上,用来介绍整个包的大致情况。我们改成如下注释看看:</p><p><strong>lib.rs</strong></p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//! # My Crate</span></span><br><span class="line"><span class="comment">//!</span></span><br><span class="line"><span class="comment">//! my_crate是一系列工具的集合,</span></span><br><span class="line"><span class="comment">//! 这些工具被用来简化特定的计算操作</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/// 将传入的数字加1</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// # Examples</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// ```</span></span><br><span class="line"><span class="comment">/// let arg = 5;</span></span><br><span class="line"><span class="comment">/// let answer = my_crate::add_one(arg);</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// assert_eq!(6, answer);</span></span><br><span class="line"><span class="comment">/// ```</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">add_one</span>(x: <span class="type">i32</span>) <span class="punctuation">-></span> <span class="type">i32</span> {</span><br><span class="line"> x + <span class="number">1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>此时生成的文档如下:</p><p><img lazyload src="/images/loading.svg" data-src="/images/image-cinf.png" alt="img" ></p><p>可以看见<code>//!</code>的注释显示在了add_one对应注释的外层,点击add_one后才会进入刚才对应的add_one用例文档</p><h2 id="使用pub-use来导出合适的公共API"><a href="#使用pub-use来导出合适的公共API" class="headerlink" title="使用pub use来导出合适的公共API"></a>使用pub use来导出合适的公共API</h2><p>这一部分的内容和use章里讲的内容基本是异曲同工,不过这里可以结合文档部分再讲一下。</p><p>这一部分解决的问题主要是:假如我们的代码结构嵌套的非常复杂的时候,那么用户在调用的时候代码就会写成<code>use::aaa::bbb::ccc::ddd::eee::fff</code>,很长很难看也很不好用,那该怎么办呢?</p><p>结合文档来看一下,例如以下代码:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//! # Art</span></span><br><span class="line"><span class="comment">//!</span></span><br><span class="line"><span class="comment">//! 一个用来建模艺术概念的代码库</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">mod</span> kinds {</span><br><span class="line"> <span class="comment">/// RYB颜色模型的三原色</span></span><br><span class="line"> <span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">PrimaryColor</span> {</span><br><span class="line"> Red,</span><br><span class="line"> Yellow,</span><br><span class="line"> Blue,</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// RYB模型的调和色</span></span><br><span class="line"> <span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">SecondaryColor</span> {</span><br><span class="line"> Orange,</span><br><span class="line"> Green,</span><br><span class="line"> Purple,</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">mod</span> utils {</span><br><span class="line"> <span class="keyword">use</span> crate::kinds::*;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// 将两种等量的原色混合生成调和色</span></span><br><span class="line"> <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">mix</span>(c1: PrimaryColor, c2: PrimaryColor) <span class="punctuation">-></span> SecondaryColor {</span><br><span class="line"> <span class="comment">// --略--</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>会生成以下文档:</p><p><img lazyload src="/images/loading.svg" data-src="/images/image-imzx.png" alt="img" ></p><p>可见暴露在外的只有kinds和utils,而我们高频使用的应该是kinds和utils的内容,这部分却要点进二级目录才能看到具体有什么。</p><p>要解决这个问题,我们只需要在结构外use一遍,把里面的结构暴露到外面即可。如下:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//! # Art</span></span><br><span class="line"><span class="comment">//!</span></span><br><span class="line"><span class="comment">//! 一个用来建模艺术概念的代码库</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">use</span> self::kinds::PrimaryColor;</span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">use</span> self::kinds::SecondaryColor;</span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">use</span> self::utils::mix;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">mod</span> kinds {</span><br><span class="line"> <span class="comment">/// RYB颜色模型的三原色</span></span><br><span class="line"> <span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">PrimaryColor</span> {</span><br><span class="line"> Red,</span><br><span class="line"> Yellow,</span><br><span class="line"> Blue,</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// RYB模型的调和色</span></span><br><span class="line"> <span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">SecondaryColor</span> {</span><br><span class="line"> Orange,</span><br><span class="line"> Green,</span><br><span class="line"> Purple,</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">mod</span> utils {</span><br><span class="line"> <span class="keyword">use</span> crate::kinds::*;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// 将两种等量的原色混合生成调和色</span></span><br><span class="line"> <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">mix</span>(c1: PrimaryColor, c2: PrimaryColor) <span class="punctuation">-></span> SecondaryColor {</span><br><span class="line"> <span class="comment">// --略--</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>以上代码我们只是在开头添加了几句<code>pub use</code>,生成的文档如下</p><p><img lazyload src="/images/loading.svg" data-src="/images/image-klpp.png" alt="img" ></p><p>可见我们<code>pub use</code>的函数也暴露在文档首页了;不过具体注释还是要点进去才能看见</p><p>同时,我们在文件外使用这些函数的时候也从:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pub use art::kinds::PrimaryColor;</span><br><span class="line">pub use art::kinds::SecondaryColor;</span><br><span class="line">pub use art::utils::mix;</span><br></pre></td></tr></table></figure></div><p>变成了:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pub use art::PrimaryColor;</span><br><span class="line">pub use art::SecondaryColor;</span><br><span class="line">pub use art::mix;</span><br></pre></td></tr></table></figure></div><h2 id="发布你的包"><a href="#发布你的包" class="headerlink" title="发布你的包"></a>发布你的包</h2><p>代码完成后,发布包只需按照以下几个步骤</p><ol><li><p>创建crates.io账户</p><p>创建省略</p><p>创建完毕后需要获取api令牌,并使用cargo进行本地身份验证,如:</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo login abcdefghijklmnopqrstuvwxyz012345</span><br></pre></td></tr></table></figure></div><p>这句命令会把你的令牌存入<code>~/.cargo/credentials</code></p></li><li><p>为包添加元数据</p><p>在<code>Cargo.toml</code>里面可以配置包名,描述,版本等等信息,填写示例如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[package]</span><br><span class="line">name = "guessing_game"</span><br><span class="line">version = "0.1.0"</span><br><span class="line">authors = ["Your Name <[email protected]>"]</span><br><span class="line">edition = "2018"</span><br><span class="line">description = "A fun game where you guess what number the computer has chosen."</span><br><span class="line">license = "MIT OR Apache-2.0"</span><br></pre></td></tr></table></figure></div></li><li><p>发布:执行<code>cargo publish</code>即可完成发布</p></li><li><p>更新:只需要更改<strong>toml</strong>里的<strong>version</strong>字段,再重新<strong>publish</strong>就能完成新版本的更新了</p></li><li><p>撤回:执行<code>cargo yank --vers 1.0.1</code>即可撤回指定版本的包;如果操作失误了,也可以撤回你的撤回:<code>cargo yank --vers 1.0.1 --undo</code>;但需要注意的是,撤回不会删除任何代码,只能阻止别人的代码下载或指定这个版本的包为依赖</p></li></ol><h1 id="Cargo工作空间"><a href="#Cargo工作空间" class="headerlink" title="Cargo工作空间"></a>Cargo工作空间</h1><p>工作空间的概念可以把项目的代码划分成若干个包,在互相关联的同时更方便的协同开发。</p><h2 id="创建工作空间"><a href="#创建工作空间" class="headerlink" title="创建工作空间"></a>创建工作空间</h2><p>这时候我们需要新建一个文件夹,名为<strong>add</strong>,直接新建一个文件夹即可,不需要<code>cargo new</code>命令创建。</p><p>然后,我们再自己建一个<code>Cargo.toml</code>文件,填入以下内容</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[workspace]</span><br><span class="line"></span><br><span class="line">members = [</span><br><span class="line"> "adder",</span><br><span class="line">]</span><br></pre></td></tr></table></figure></div><p>这个toml文件不像我们之前碰到的toml一样有一堆字段,他只包含一个工作空间字段,声明了工作空间下的成员有一个<strong>adder</strong>,这个<strong>adder</strong>我们还没创建,现在来创建一下。</p><p>使用cargo new字段创建一个二进制包:<code>cargo new adder</code></p><p>这个时候我们就已经可以在add目录下使用<code>cargo build</code>构建整个工作空间了。</p><h2 id="在工作空间里创建第二个包"><a href="#在工作空间里创建第二个包" class="headerlink" title="在工作空间里创建第二个包"></a>在工作空间里创建第二个包</h2><p>我们可以用<code>cargo new add-one --lib</code>来创建一个代码包,当然,我们同时也得在toml文件加上</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[workspace]</span><br><span class="line"></span><br><span class="line">members = ["adder", "add-one"]</span><br></pre></td></tr></table></figure></div><p>然后,我们再在<strong>add-one</strong>里的<strong>lib.rs</strong>写上我们可以供外部调用的接口函数,如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pub fn add_one(x: i32) -> i32 {</span><br><span class="line"> x + 1</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>那我们在adder里要怎么调用add-one里的接口呢?</p><p>工作空间是不会自动为你假设出依赖关系的,所以需要自己在toml里显式的指定,例如我们要在adder里调用add-one的函数,就<strong>要在adder的toml里添加以下字段</strong>:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[dependencies]</span><br><span class="line"></span><br><span class="line">add-one = { path = "../add-one" }</span><br></pre></td></tr></table></figure></div><p>这一部分指定了adder需要依赖的包,名字是什么,对应的包路径在哪。显式指定后,<a href="http://我们就可以在adder的main.rs/">我们就可以在<strong>adder</strong>的<strong>main.rs</strong></a>里编写代码,并<strong>使用add-one里的接口</strong>了,如下:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> add_one;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num</span> = <span class="number">10</span>;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"Hello, world! {} plus one is {}!"</span>, num, add_one::<span class="title function_ invoke__">add_one</span>(num));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>最后,我们再在根目录add下build编译并运行。</p><p>运行和往常有所不同,因为一个工作空间可能有很多个二进制包,所以我们需要指定运行哪个二进制包,加上<code>-p</code>参数即可,如:<code>cargo run -p adder</code></p><p>然后就可以看到正常的输出了</p><h2 id="在工作空间中依赖外部包"><a href="#在工作空间中依赖外部包" class="headerlink" title="在工作空间中依赖外部包"></a>在工作空间中依赖外部包</h2><p>和以前一样,也是在toml里指定即可,例如这里我们给add-one引入一个rand包,如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[dependencies]</span><br><span class="line"></span><br><span class="line">rand = "0.3.14"</span><br></pre></td></tr></table></figure></div><p>再执行cargo build,就可以自动的下载安装rand包了。</p><hr><p>这里值得一提的是,Cargo.lock文件只在add根目录下有,这是为了保证工作空间下所有包的依赖都是同一版本的,一是节约了磁盘空间,二是确保工作空间下的包互相之间是兼容的,避免奇奇怪怪的问题。</p><h2 id="工作空间下进行测试"><a href="#工作空间下进行测试" class="headerlink" title="工作空间下进行测试"></a>工作空间下进行测试</h2><p>和往常一样编写测试代码,然后在根目录下执行cargo test即可自动完成所有测试。</p><p>如:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">add_one</span>(x: <span class="type">i32</span>) <span class="punctuation">-></span> <span class="type">i32</span> {</span><br><span class="line"> x + <span class="number">1</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#[cfg(test)]</span></span><br><span class="line"><span class="keyword">mod</span> tests {</span><br><span class="line"><span class="keyword">use</span> super::*;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[test]</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">it_works</span>() {</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="number">3</span>, <span class="title function_ invoke__">add_one</span>(<span class="number">2</span>));</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">$ cargo test</span><br><span class="line">warning: <span class="keyword">virtual</span> workspace defaulting to `resolver = <span class="string">"1"</span>` despite one or more workspace members being on edition <span class="number">2021</span> which implies `resolver = <span class="string">"2"</span>`</span><br><span class="line">note: to keep the current resolver, specify `workspace.resolver = <span class="string">"1"</span>` <span class="keyword">in</span> the workspace root<span class="symbol">'s</span> manifest</span><br><span class="line">note: to <span class="keyword">use</span> the edition <span class="number">2021</span> resolver, specify `workspace.resolver = <span class="string">"2"</span>` <span class="keyword">in</span> the workspace root<span class="symbol">'s</span> manifest</span><br><span class="line">note: <span class="keyword">for</span> <span class="title class_">more</span> details see https:<span class="comment">//doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions</span></span><br><span class="line"> Compiling add-one v0.<span class="number">1.0</span> (E:\Code\rust\learning\add\add-one)</span><br><span class="line"> Compiling adder v0.<span class="number">1.0</span> (E:\Code\rust\learning\add\adder)</span><br><span class="line"> Finished test [unoptimized + debuginfo] <span class="title function_ invoke__">target</span>(s) <span class="keyword">in</span> <span class="number">0.29</span>s</span><br><span class="line"> Running unittests src\lib.<span class="title function_ invoke__">rs</span> (target\debug\deps\add_one-<span class="number">48</span>bf46fc7c463809.exe)</span><br><span class="line"></span><br><span class="line">running <span class="number">1</span> test</span><br><span class="line">test tests::it_works ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. <span class="number">1</span> passed; <span class="number">0</span> failed; <span class="number">0</span> ignored; <span class="number">0</span> measured; <span class="number">0</span> filtered out; finished <span class="keyword">in</span> <span class="number">0.00</span>s</span><br><span class="line"></span><br><span class="line"> Running unittests src\main.<span class="title function_ invoke__">rs</span> (target\debug\deps\adder-<span class="number">174</span>daa247398ed5b.exe)</span><br><span class="line"></span><br><span class="line">running <span class="number">0</span> tests</span><br><span class="line"></span><br><span class="line">test result: ok. <span class="number">0</span> passed; <span class="number">0</span> failed; <span class="number">0</span> ignored; <span class="number">0</span> measured; <span class="number">0</span> filtered out; finished <span class="keyword">in</span> <span class="number">0.00</span>s</span><br><span class="line"></span><br><span class="line"> Doc-tests add-one</span><br><span class="line"></span><br><span class="line">running <span class="number">0</span> tests</span><br><span class="line"></span><br><span class="line">test result: ok. <span class="number">0</span> passed; <span class="number">0</span> failed; <span class="number">0</span> ignored; <span class="number">0</span> measured; <span class="number">0</span> filtered out; finished <span class="keyword">in</span> <span class="number">0.00</span>s</span><br></pre></td></tr></table></figure></div><h1 id="使用cargo-install来安装可执行程序"><a href="#使用cargo-install来安装可执行程序" class="headerlink" title="使用cargo install来安装可执行程序"></a>使用cargo install来安装可执行程序</h1><p><code>cargo install</code>的主要作用是用来安装别人分享的二进制包的,可以直接在命令行使用别人写好的工具。安装会装在<strong>rust安装的根目录下的bin文件夹</strong>里,所以如果要直接使用的话,需要配置一下环境变量,把这个目录加进去。</p><p>例如:<code>cargo install ripgrep</code></p><p>具体就没什么好演示的了,这是一个用来搜索文件的小工具。</p><h1 id="使用自定义命令扩展Cargo的功能"><a href="#使用自定义命令扩展Cargo的功能" class="headerlink" title="使用自定义命令扩展Cargo的功能"></a>使用自定义命令扩展Cargo的功能</h1><p>cargo xxx可以运行xxx这个二进制文件,所以如果你用cargo install安装了某个扩展,也可以用cargo xxx来运行</p>]]></content>
<summary type="html"><p>这一章就主要讲讲Cargo这个工具的一些用途,主要是以下几个部分,没有涉猎到的可以在<a class="link" href="https://doc.rust-lang.org/cargo/" >官网<i class="fa-solid fa-arrow-up-rig</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】13. 闭包与迭代器</title>
<link href="https://twosix.page/2024/01/18/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9113-%E9%97%AD%E5%8C%85%E4%B8%8E%E8%BF%AD%E4%BB%A3%E5%99%A8/"/>
<id>https://twosix.page/2024/01/18/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9113-%E9%97%AD%E5%8C%85%E4%B8%8E%E8%BF%AD%E4%BB%A3%E5%99%A8/</id>
<published>2024-01-18T12:57:44.000Z</published>
<updated>2024-07-04T15:52:28.393Z</updated>
<content type="html"><![CDATA[<h1 id="闭包:能够捕获环境的匿名函数"><a href="#闭包:能够捕获环境的匿名函数" class="headerlink" title="闭包:能够捕获环境的匿名函数"></a>闭包:能够捕获环境的匿名函数</h1><p>如这个小节的标题所示,闭包其实就是一个匿名函数,可以接收变量,也可以返回值,主要是用来实现一些代码复用和自定义的行为。</p><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>首先,闭包的基本定义方法如下</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span> = <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">c</span> = |a, b|{a+b};</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">d</span> = |a, b| a-b;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}, {}"</span>, <span class="title function_ invoke__">c</span>(a, b), <span class="title function_ invoke__">d</span>(a, b));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol><li>通过**||**包裹住传入的参数,通过逗号隔开</li><li>花括号内定义函数的行为。当然,对于简短的函数,例如a+b这种一条表达式直接返回值的,也可以不用花括号,如d</li></ol><p>闭包与函数不同的是</p><ol><li><p>闭包内<strong>不强迫显示标注类型</strong>,因为考虑到闭包这种通常使用的场景就是一个狭窄的上下文内,进行一个暂时的函数定义,而不会被广泛的定义。So, rust的设计者认为这种场景下干脆默认直接交给编译器推理就行。所以,闭包也有编译期固定类型的特性,闭包第一次被调用的时候,就按照第一次传入的参数被固定了,那第二次调用的时候是无法使用其他类型的参数传入的。如下面的代码会报错:<code>error[E0308]: arguments to this function are incorrect</code></p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span> = <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">closure</span> = |a, b|{a+b};</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, <span class="title function_ invoke__">closure</span>(a, b));</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">c</span> = <span class="number">1.1</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">d</span> = <span class="number">2.2</span>;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, <span class="title function_ invoke__">closure</span>(c, d));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>如果要实现复杂的闭包,用泛型等,自行指定类型也是没有问题的</p></li><li><p>闭包<strong>可以作为变量存储</strong>,这也就增加了更多代码复用的写法,例如作为变量存到结构体里,在结构体里按需调用之类的</p></li><li><p>闭包<strong>可以捕获环境上下文</strong>;这是一个与函数相比最大的不同点,这个就稍微展开来讲讲。</p></li></ol><h2 id="使用闭包捕获上下文环境"><a href="#使用闭包捕获上下文环境" class="headerlink" title="使用闭包捕获上下文环境"></a>使用闭包捕获上下文环境</h2><p>首先,捕获上下文是什么意思?看代码就知道了:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">closure</span> = |num| num==a;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, <span class="title function_ invoke__">closure</span>(<span class="number">1</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在代码里可以看到,我们的闭包在没有传入a的情况下,获取到了闭包外的值a来进行一系列的处理。而普通函数如果不对a进行显示的传入的话,是不可能获得a的值的,这就是所谓的捕获上下文的能力。</p><p>那么问题来了,rust里的核心机制就是所有权,参数到了函数里,所有权也会转到函数中,那a在闭包内的所有权是怎么变化的呢?</p><p>我们换个例子来讲,因为a是整型,是存储在栈里的变量,在传入传出的时候是无脑的复制一份所有权,所以不具有代表性,我们换成数组。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">closure</span> = |num| num ==a[<span class="number">0</span>];</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, <span class="title function_ invoke__">closure</span>(<span class="number">1</span>));</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, a);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>闭包里面包含了三个trait,分别是<strong>FnOnce</strong>, <strong>FnMut</strong>, <strong>Fn</strong>,而对于每一个闭包,至少实现了这三个trait中的一个。</p><ol><li><strong>FnOnce</strong>是在闭包定义时会调用的,而且也只会调用一次的trait,他会把捕获到的环境变量所有权转移到闭包环境内。</li><li><strong>FnMut</strong>是可变的借用捕获的变量,不夺取所有权</li><li><strong>Fn</strong>则是不可变的借用</li></ol><p>闭包会对变量实现哪个trait是自动的,主要取决于你在闭包内用变量来干了什么。例如我的数组例子里,是可以运行成功的,因为我只简单的使用了一下数组内的元素进行==的比较,并没有设计数值修改,所以闭包自动选择了不可变借用的形式。如以下形式,就是可变借用:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]; <span class="comment">// 注意a要改成可变</span></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">closure</span> = |tmp| {</span><br><span class="line"> a = tmp;</span><br><span class="line"> a</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span> = <span class="built_in">vec!</span>[<span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, <span class="title function_ invoke__">closure</span>(b));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>如果你希望夺取所有权的话,有一个关键词<strong>move</strong>可以实现这么一个功能。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">closure</span> = <span class="keyword">move</span> |num| num == a[<span class="number">0</span>];</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, <span class="title function_ invoke__">closure</span>(<span class="number">1</span>));</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, a);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>定义为move后因为a的所有权被移到了闭包内,因此代码报错,无法运行。</p><p>除了自动实现的以外,你也可以采用之前讲的trait相关内容,为闭包指定trait类型,也可以重载实现trait以自定义更多的行为,这里就不多做讲解了。</p><h1 id="迭代器:处理元素序列"><a href="#迭代器:处理元素序列" class="headerlink" title="迭代器:处理元素序列"></a>迭代器:处理元素序列</h1><p>迭代器也并不是一个新概念了,简单来说,迭代器主要就是用来遍历序列的,主要是解决传统遍历方式需要程序员自己判断序列是什么时候结束的问题,用迭代器就可以省去每次遍历都要定义一个=0的量,获取一次序列大小的逻辑。</p><p>迭代器的本质是一个<code>Iterator</code>的trait,如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">pub trait Iterator {</span><br><span class="line"> type Item;</span><br><span class="line"></span><br><span class="line"> fn next(&mut self) -> Option<Self::Item>;</span><br><span class="line"></span><br><span class="line"> // 这里省略了由Rust给出的默认实现方法</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>所以迭代器本质上就是不断的调用next方法,生成下一个元素的值,包装成Option然后返回。</p><p>迭代器的生成是lazy的,和python的yield一样,只有当你遍历到那个元素的时候,迭代器才会使用next生成一个元素,然后返回到外面提供用户使用,在没有使用到的时候什么也不会发生,这在一定程度上节约了内存的使用。</p><p>上面的代码有一些新的语法,<code>type Item</code>,<code>Self::Item</code>等,这个Item的功能是用来指定一个数据类型,后面会讲。</p><h2 id="创建一个迭代器"><a href="#创建一个迭代器" class="headerlink" title="创建一个迭代器"></a>创建一个迭代器</h2><h3 id="iter"><a href="#iter" class="headerlink" title=".iter()"></a>.iter()</h3><p>创建一个迭代器有很多方法,最基础的就是.iter()</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a_iter</span> = a.<span class="title function_ invoke__">iter</span>();</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> a_iter {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, i);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>以上代码就是一个最简单的,使用<code>.iter()</code>创建一个列表的迭代器来遍历列表的示例。</p><p>当然,既然说过迭代器的本质是不断调用next方法,所以我们也可以使用next方法来使用迭代器。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">a_iter</span> = a.<span class="title function_ invoke__">iter</span>();</span><br><span class="line"> <span class="built_in">assert_eq!</span>(a_iter.<span class="title function_ invoke__">next</span>(), <span class="title function_ invoke__">Some</span>(&<span class="number">1</span>));</span><br><span class="line"> <span class="built_in">assert_eq!</span>(a_iter.<span class="title function_ invoke__">next</span>(), <span class="title function_ invoke__">Some</span>(&<span class="number">2</span>));</span><br><span class="line"> <span class="built_in">assert_eq!</span>(a_iter.<span class="title function_ invoke__">next</span>(), <span class="title function_ invoke__">Some</span>(&<span class="number">3</span>));</span><br><span class="line"> <span class="built_in">assert_eq!</span>(a_iter.<span class="title function_ invoke__">next</span>(), <span class="literal">None</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里需要注意的是,<strong>a_iter必须定义为可变</strong>,因为调用next是会不断的吃掉上一个变量,替换成下一个变量的迭代器。</p><h3 id=""><a href="#" class="headerlink" title=""></a></h3><h2 id="使用迭代器"><a href="#使用迭代器" class="headerlink" title="使用迭代器"></a>使用迭代器</h2><p>因为next方法是会不断“吃掉”上一个元素的,因此迭代器的使用也伴随着迭代器的消耗。基本的for循环就是使用迭代器的一个方法。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a_iter</span> = a.<span class="title function_ invoke__">iter</span>();</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> a_iter {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> a_iter {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, i);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码运行将会报错,可以看到,a_iter在被第一个for循环使用完之后,就无法继续使用了。</p><p>除了基本的循环外,还有很多提供便捷功能的迭代器相关方法,它们也同样会消耗迭代器。</p><ol><li><p><code>sum</code>方法会自动生成所有迭代器,并返回其中所有元素的和。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a_iter</span> = a.<span class="title function_ invoke__">iter</span>();</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">total</span>: <span class="type">i32</span> = a_iter.<span class="title function_ invoke__">sum</span>();</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, total);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>需要注意的是,这里必须把a_iter.sum()的结果存储到变量中打印,并显示标注total的类型,但这是后面再讲的问题了</p></li><li><p><code>collect</code>方法可以自动收集所有迭代器生成的元素,返回一个列表。</p></li></ol><p>除了消耗迭代器返回元素的方法外,还有一种方法可以消耗旧的迭代器,生成新的符合一定要求的迭代器。这种方法成为<strong>迭代器适配器</strong></p><ol><li><p><code>map</code>方法可以接收一个闭包作为参数,自定义一个新的迭代器出来。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a_iter</span> = a.<span class="title function_ invoke__">iter</span>();</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a2_iter</span> = a_iter.<span class="title function_ invoke__">map</span>(|x| x * <span class="number">2</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span>: <span class="type">Vec</span><<span class="type">i32</span>> = a2_iter.<span class="title function_ invoke__">collect</span>();</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, b);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这个示例中,我们使用map方法,生成一个会把原来元素*2再返回的迭代器,再使用collect方法收集成新的列表并打印输出</p></li><li><p><code>filter</code>方法同样也是接收闭包为参数,但这个闭包必须是返回一个<strong>bool</strong>值,利用闭包的bool结果,filter方法会过滤掉返回结果不为true的迭代器。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="built_in">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>];</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a_iter</span> = a.<span class="title function_ invoke__">into_iter</span>();</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a2_iter</span> = a_iter.<span class="title function_ invoke__">filter</span>(|x| x % <span class="number">2</span> == <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span>: <span class="type">Vec</span><<span class="type">i32</span>> = a2_iter.<span class="title function_ invoke__">collect</span>();</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, b);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里使用的是into_iter,因为a.iter()返回的是一个<strong>引用</strong>,所以在执行%运算的时候会报类型不匹配的错误;要么解引用,要么用into_iter直接夺取所有权,这里我偷了个懒。(PS:上面的乘法能编译通过是因为乘法这种基本运算会自动解引用。)</p><p>当然,既然filter和map的传入参数是闭包,自然也可以捕获环境上下文,感兴趣可以自己试试,这里就不多写演示代码了。</p></li></ol><p>还有很多方法,例如zip(),skip(),这里就没办法一一展开了</p><h2 id="自定义迭代器"><a href="#自定义迭代器" class="headerlink" title="自定义迭代器"></a>自定义迭代器</h2><p>迭代器本质上是一个Iterator trait,所以我们自然也可以定义一个结构体,为他实现一个Iterator trait,进而实现一个我们自定义的迭代器。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Counter</span> {</span><br><span class="line"> count: <span class="type">u32</span>,</span><br><span class="line">}</span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Counter</span> {</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">new</span>() <span class="punctuation">-></span> Counter {</span><br><span class="line"> Counter { count: <span class="number">0</span> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Iterator</span> <span class="keyword">for</span> <span class="title class_">Counter</span> {</span><br><span class="line"> <span class="keyword">type</span> <span class="title class_">Item</span> = <span class="type">u32</span>;</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">next</span>(&<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-></span> <span class="type">Option</span><<span class="keyword">Self</span>::Item> {</span><br><span class="line"> <span class="keyword">self</span>.count += <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">self</span>.count < <span class="number">6</span> {</span><br><span class="line"> <span class="title function_ invoke__">Some</span>(<span class="keyword">self</span>.count)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="literal">None</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">counter</span> = Counter::<span class="title function_ invoke__">new</span>();</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> counter {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, i);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段示例里,我们就定义了一个结构体<strong>Counter</strong>,内置一个u32变量<strong>count</strong>,初始值为0,然后为Counter实现了一个Iterator trait,让Counter结构体变成了一个迭代器,迭代的逻辑就是不断的递增结构体内的count变量,直到count>=6。</p><h2 id="迭代器的性能分析"><a href="#迭代器的性能分析" class="headerlink" title="迭代器的性能分析"></a>迭代器的性能分析</h2><p>例如说我们现在有一个功能,需要在一堆字符串中,找到所有包含某个子串的字符串。那最简单的,利用循环的写法可以写成如下形式:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">search</span><<span class="symbol">'a</span>>(query: &<span class="type">str</span>, contents: &<span class="symbol">'a</span> <span class="type">str</span>) <span class="punctuation">-></span> <span class="type">Vec</span><&<span class="symbol">'a</span> <span class="type">str</span>> {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">results</span> = <span class="type">Vec</span>::<span class="title function_ invoke__">new</span>();</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">line</span> <span class="keyword">in</span> contents.<span class="title function_ invoke__">lines</span>() {</span><br><span class="line"> <span class="keyword">if</span> line.<span class="title function_ invoke__">contains</span>(query) {</span><br><span class="line"> results.<span class="title function_ invoke__">push</span>(line);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> results</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>那在知道迭代器之后,我们自然也可以想到可以通过迭代器的filter方法,过滤出只包含这个子串的迭代器,迭代器的版本可以这么写:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">search</span><<span class="symbol">'a</span>>(query: &<span class="type">str</span>, contents: &<span class="symbol">'a</span> <span class="type">str</span>) <span class="punctuation">-></span> <span class="type">Vec</span><&<span class="symbol">'a</span> <span class="type">str</span>> {</span><br><span class="line"> contents.<span class="title function_ invoke__">lines</span>()</span><br><span class="line"> .<span class="title function_ invoke__">filter</span>(|line| line.<span class="title function_ invoke__">contains</span>(query))</span><br><span class="line"> .<span class="title function_ invoke__">collect</span>()</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>那这两种写法有什么区别呢,多抽象一层迭代器的话,会不会有性能损失?</p><p>答案是不会的。</p><p>Rust官方书籍里用一本小说做了一次bench mark,结果表面,迭代器的写法在经过优化后比for循环更快。</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">test</span> bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)</span><br><span class="line"><span class="built_in">test</span> bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)</span><br></pre></td></tr></table></figure></div><p>迭代器是Rust里的一种零开销抽象,所以我们在使用的时候根本不需要担心会不会引入额外的运行成本。</p><p>至于快了的那一点,本质上是因为迭代器在编译过程中会运行一次展开优化,例如本来要运行12次的循环代码,如果是迭代器的话,在编译之后会展开成重复执行12次的代码,减少了循环控制时候的开销。</p><p>但个人觉得可以忽略不计,大可依照个人习惯的去选择使用这两种写法,benchmark本质上只是证明了iter并不会引入额外开销,放心使用。</p>]]></content>
<summary type="html"><h1 id="闭包:能够捕获环境的匿名函数"><a href="#闭包:能够捕获环境的匿名函数" class="headerlink" title="闭包:能够捕获环境的匿名函数"></a>闭包:能够捕获环境的匿名函数</h1><p>如这个小节的标题所示,闭包其实就是一个匿名函</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】12. 编写一个命令行程序</title>
<link href="https://twosix.page/2023/05/12/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9112-%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%A8%8B%E5%BA%8F/"/>
<id>https://twosix.page/2023/05/12/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9112-%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%A8%8B%E5%BA%8F/</id>
<published>2023-05-12T13:34:53.000Z</published>
<updated>2024-07-04T15:52:28.392Z</updated>
<content type="html"><![CDATA[<blockquote><p>本章节我们将开始学习编写一个小项目——开发一个能够和文件系统交互并处理命令行输入、输出的工具</p></blockquote><h1 id="基本功能实现"><a href="#基本功能实现" class="headerlink" title="基本功能实现"></a>基本功能实现</h1><p>首先自然是新建一个项目,名为<em>minigrep</em></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo new minigrep</span><br></pre></td></tr></table></figure></div><p>实现这一个工具的首要任务自然是接收命令行的参数,例如我们要实现在一个文件里搜索字符串,就得在运行时接收两个参数,一个待搜索的字符串,一个搜索的文件名,例如</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo run string filename.txt</span><br></pre></td></tr></table></figure></div><p>这种基础的功能自然是已经有一些现成的crate可以使用来实现这些功能的了,不过因为我们的主要目的是学习,所以接下来我们会从零开始实现这些功能。</p><h2 id="读取参数值"><a href="#读取参数值" class="headerlink" title="读取参数值"></a>读取参数值</h2><p>这一部分我们需要用到标准库里的<code>std::env::args</code>函数,这个函数会返回一个命令行参数的迭代器,使得程序可以读取所有传递给它的命令行参数值,放到一个动态数组里。</p><p>用例:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> println!("{:?}", args);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码简单来说就是通过<code>env::args()</code>读取命令行参数的迭代器,再通过<code>collect()</code>函数自动遍历迭代器把参数收集到一个动态数组里并返回。</p><p>接下来我们用终端运行代码,传入参数试试</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cargo run hahha arg </span><br><span class="line"> Compiling minigrep v0.1.0 (E:\Code\rust\minigrep)</span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 1.81s</span><br><span class="line"> Running `target\debug\minigrep.exe hahha arg`</span><br><span class="line">["target\\debug\\minigrep.exe", "hahha", "arg"]</span><br></pre></td></tr></table></figure></div><p>这里的输出的第一个参数是当前运行的程序的二进制文件入口路径,这一功能主要方便程序员打印程序名称/路径,后续才是我们传入的参数字符串,一般情况下我们忽略第一个参数只处理后面两个即可。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> println!("{:?}", args);</span><br><span class="line"> </span><br><span class="line"> let query = &args[1];</span><br><span class="line"> let filename = &args[2];</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", query);</span><br><span class="line"> println!("In file {}", filename);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>接下来我们再运行以下这段代码,输出一切正常的话,第一步接收命令行参数的任务我们就完成啦!</p><h2 id="读取文件"><a href="#读取文件" class="headerlink" title="读取文件"></a>读取文件</h2><p>既然是要在文件内容里搜索指定字符串,自然要读取文件的内容了。但在这之前我们要先有一个文件,现在我们在项目的<strong>根目录</strong>下,新建一个<code>poem.txt</code>文件,内容就选择书上给的这首诗吧:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">I'm nobody! Who are you?</span><br><span class="line">Are you nobody, too?</span><br><span class="line">Then there's a pair of us - don't tell!</span><br><span class="line">They'd banish us, you know.</span><br><span class="line"></span><br><span class="line">How dreary to be somebody!</span><br><span class="line">How public, like a frog</span><br><span class="line">To tell your name the livelong day</span><br><span class="line">To an admiring bog!</span><br></pre></td></tr></table></figure></div><p>接下来,我们就可以在代码里读取这个文件了,也很简单,用之前使用过的<code>fs</code>库即可,代码如下。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line">use std::fs;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> println!("{:?}", args);</span><br><span class="line"> </span><br><span class="line"> let query = &args[1];</span><br><span class="line"> let filename = &args[2];</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", query);</span><br><span class="line"> println!("In file {}", filename);</span><br><span class="line"></span><br><span class="line"> let contents = fs::read_to_string(filename).expect("Error in reading file");</span><br><span class="line"> println!("With text:\n{}", contents)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>接下来,再运行以下这段代码,用命令行传递参数的方法传递我们的文件名poem.txt</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">cargo run bog poem.txt </span><br><span class="line"> Compiling minigrep v0.1.0 (E:\Code\rust\minigrep)</span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 0.63s</span><br><span class="line"> Running `target\debug\minigrep.exe bog poem.txt`</span><br><span class="line">["target\\debug\\minigrep.exe", "bog", "poem.txt"]</span><br><span class="line">Searching for bog</span><br><span class="line">In file poem.txt</span><br><span class="line">With text:</span><br><span class="line">I'm nobody! Who are you?</span><br><span class="line">Are you nobody, too?</span><br><span class="line">Then there's a pair of us - don't tell!</span><br><span class="line">They'd banish us, you know.</span><br><span class="line"></span><br><span class="line">How dreary to be somebody!</span><br><span class="line">How public, like a frog</span><br><span class="line">To tell your name the livelong day</span><br><span class="line">To an admiring bog!</span><br></pre></td></tr></table></figure></div><p>可见,顺利的输出了我们的文件内容,代码没有问题。</p><p>到此为止,我们的基本功能就实现完了。但目前为止,我们现在写的代码都一股脑的堆积在<code>main.rs</code>里,非常简单,但也不具备可扩展性以及可维护性,远远称不上一个”项目”。接下来我们就要用到之前学习的模块化管理的知识,重构一下我们的代码,让它变得更规范化。</p><h1 id="模块化与错误处理"><a href="#模块化与错误处理" class="headerlink" title="模块化与错误处理"></a>模块化与错误处理</h1><h2 id="问题与计划"><a href="#问题与计划" class="headerlink" title="问题与计划"></a>问题与计划</h2><p>接下来我们将针对以下四个问题,来制定计划重构我们的代码:</p><ol><li>功能如果都堆积在main函数里的话,随着我们的功能越来越多,这个文件的代码将会变得越来越复杂,越来越难让人理解,更难去测试,耦合程度很高。所以我们要把函数拆分开来,一个函数负责一个任务</li><li>query和filename是存储程序配置的,contents是用于业务逻辑的,当一个作用域的变量越来越多时,我们就很难准确的追踪每个变量的实际用途;所以我们最好应该把用途相同的变量合并到一个结构体里,方便管理也明确用途。</li><li>错误处理方面处理的不够详细,读取文件失败可能有很多原因,我们应该准确定位到每个原因,抛出相关的提示,给用户提供更有效的排错信息。</li><li>错误管理也应该模块化,例如当用户没有指定运行参数时,底层报错是一个数组越位时,抛出错误”index out of bounds”,这无法帮助用户有效的理解问题本身,我们最好把用于错误处理的代码集中管理,更加方便的处理相关的错误逻辑,也方便为用户打印有意义,便于理解的报错信息。</li></ol><h2 id="二进制项目的组织结构"><a href="#二进制项目的组织结构" class="headerlink" title="二进制项目的组织结构"></a>二进制项目的组织结构</h2><p>Rust社区对于程序的组织结构有一套自己的原则</p><ol><li>程序拆分为main.rs和lib.rs,实际的逻辑放在lib.rs,main.rs只作简单调用</li><li>如果逻辑相对简单,再考虑留在main.rs,保留在main中的代码量应该小到可以一眼看出这段代码的正确性</li></ol><p>也就是,main负责运行,lib负责实际逻辑,同时由上一章自动化测试可知,如果我们把逻辑放到lib.rs也更方便我们编写集成测试的代码。</p><p>所以,我们这个项目的main函数功能大概如下</p><ul><li>处理命令行参数</li><li>程序配置的变量</li><li>调用lib.rs中的run函数</li><li>对run函数进行错误处理</li></ul><p>接下来我们就可以开始按照以上原则重构了</p><h2 id="分离基本功能逻辑"><a href="#分离基本功能逻辑" class="headerlink" title="分离基本功能逻辑"></a>分离基本功能逻辑</h2><h3 id="提取解析参数代码"><a href="#提取解析参数代码" class="headerlink" title="提取解析参数代码"></a>提取解析参数代码</h3><p>首先,我们把解析参数做成一个函数,提供main函数调用,方便后面再把这个功能转移到lib</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line">use std::fs;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> println!("{:?}", args);</span><br><span class="line"> </span><br><span class="line"> let (query, filename) = parse_config(&args);</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", query);</span><br><span class="line"> println!("In file {}", filename);</span><br><span class="line"></span><br><span class="line"> let contents = fs::read_to_string(filename).expect("Error in reading file");</span><br><span class="line"> println!("With text:\n{}", contents)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn parse_config(args: &Vec<String>) -> (&str, &str){</span><br><span class="line"> let query = &args[1];</span><br><span class="line"> let filename = &args[2];</span><br><span class="line"> (query, filename)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="组合参数值"><a href="#组合参数值" class="headerlink" title="组合参数值"></a>组合参数值</h3><p>上面的代码我们选择返回一个元组,又把一个元组拆分成两个变量,这还是有点意义不明,不方便使用,所以我们选择把这两个值放到一个结构体里,再用两个明确的变量名存储这两个参数,使得这个函数的返回值更加明确。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line">use std::fs;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> println!("{:?}", args);</span><br><span class="line"> </span><br><span class="line"> let config = parse_config(&args);</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", config.query);</span><br><span class="line"> println!("In file {}", config.filename);</span><br><span class="line"></span><br><span class="line"> let contents = fs::read_to_string(config.filename).expect("Error in reading file");</span><br><span class="line"> println!("With text:\n{}", contents)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">struct Config{</span><br><span class="line"> query: String,</span><br><span class="line"> filename: String,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn parse_config(args: &Vec<String>) -> Config{</span><br><span class="line"> let config = Config{</span><br><span class="line"> query: args[1].clone(),</span><br><span class="line"> filename: args[2].clone(),</span><br><span class="line"> };</span><br><span class="line"> config</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里,我们定义了一个<code>Config</code>结构体来存储我们的配置值,同时,秉持着结构体最好还是持有自己变量所有权的理念,这里我们需要把字符串clone一下再存入config变量,这种做法虽然比起直接使用引用多用了一些时间和内存,但也省去写生命周期的麻烦,也增加了代码可读性,毕竟开发效率也是效率,该做的牺牲还得做。</p><p>可见,现在main函数里解析参数,使用参数的逻辑已经非常清晰了。</p><blockquote><p>有人对运行时代价很敏感,就是不喜欢用clone解决所有权问题,这个后面在13章会学习一些其他方法更有效率的处理这种情形。但对于我们现在来说,clone是一点没有问题的,因为我们这里的字符串只有读取程序参数的时候用一次clone,而且还只是两个很短的字符串,这点性能真算不上浪费。</p></blockquote><h3 id="为Config创建一个构造器"><a href="#为Config创建一个构造器" class="headerlink" title="为Config创建一个构造器"></a>为Config创建一个构造器</h3><p>这时候我们再回头看一下,其实<code>parse_config</code>这个函数和我们的<code>Config</code>结构体也是紧密相关的,因为parse_config的目的就是构造一个Config结构体变量,那我们为什么不在Config结构体里写一个构造函数来实现同样的功能,增强这个函数和Config结构体的相关性,更好理解呢</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line">use std::fs;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> println!("{:?}", args);</span><br><span class="line"> </span><br><span class="line"> let config = Config::new(&args);</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", config.query);</span><br><span class="line"> println!("In file {}", config.filename);</span><br><span class="line"></span><br><span class="line"> let contents = fs::read_to_string(config.filename).expect("Error in reading file");</span><br><span class="line"> println!("With text:\n{}", contents)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">struct Config{</span><br><span class="line"> query: String,</span><br><span class="line"> filename: String,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">impl Config {</span><br><span class="line"> fn new(args: &Vec<String>) -> Config{</span><br><span class="line"> let query = args[1].clone();</span><br><span class="line"> let filename = args[2].clone();</span><br><span class="line"> Config{query, filename}</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>于是我们的代码变成了上面这样,使用<code>Config::new</code>代替了之前的<code>parse_config</code>,并顺利运行。</p><h3 id="分离主体逻辑"><a href="#分离主体逻辑" class="headerlink" title="分离主体逻辑"></a>分离主体逻辑</h3><p>这里我们的项目主体运行逻辑就是打开文件并搜索字符串,目前我们只写了打开文件,就先把这部分拆出来,写到<code>run</code>函数里</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fn run(config: Config){</span><br><span class="line"> let contents = fs::read_to_string(config.filename)</span><br><span class="line"> .expect("Error in reading file");</span><br><span class="line"> println!("With text:\n{}", contents);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h2><p>依照计划,现在我们开始修复一下错误处理相关的逻辑</p><h3 id="改进错误提示信息"><a href="#改进错误提示信息" class="headerlink" title="改进错误提示信息"></a>改进错误提示信息</h3><p>首先,第一个能想到的错误就是,用户输入的参数不够,例如没有输入参数时,程序会报错“index out of bounds: the len is 1 but the index is 1”,但这个错误会让用户不知所云,所以我们需要完善一下报错提示:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">impl Config {</span><br><span class="line"> fn new(args: &Vec<String>) -> Config{</span><br><span class="line"> if args.len() < 3 {</span><br><span class="line"> panic!("Not enough arguments");</span><br><span class="line"> }</span><br><span class="line"> let query = args[1].clone();</span><br><span class="line"> let filename = args[2].clone();</span><br><span class="line"> Config{query, filename}</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在参数数量少于3个的时候,我们主动抛出panic,提示参数不够,这时的报错信息就更人性化了。</p><h3 id="返回Result而不是直接Panic"><a href="#返回Result而不是直接Panic" class="headerlink" title="返回Result而不是直接Panic"></a>返回Result而不是直接Panic</h3><p>对于函数里的错误,使用Result作为返回结果,让调用函数的用户决定是否panic的方法会更加友好一点。所以我们再修改一下这个new函数,让它在运行成功时返回一个Config变量,在失败时返回报错信息</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">impl Config {</span><br><span class="line"> fn new(args: &Vec<String>) -> Result<Config, &str>{</span><br><span class="line"> if args.len() < 3 {</span><br><span class="line"> return Err("Not enough arguments")</span><br><span class="line"> }</span><br><span class="line"> let query = args[1].clone();</span><br><span class="line"> let filename = args[2].clone();</span><br><span class="line"> Ok(Config{query, filename})</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>因为修改了返回值,所以我们还需要对应修改一下main函数的逻辑</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line">use std::fs;</span><br><span class="line">use std::process;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> </span><br><span class="line"> let config = Config::new(&args).unwrap_or_else(|err|{</span><br><span class="line"> println!("Problem parsing args: {}", err);</span><br><span class="line"> process::exit(1);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", config.query);</span><br><span class="line"> println!("In file {}", config.filename);</span><br><span class="line"></span><br><span class="line"> let contents = fs::read_to_string(config.filename).expect("Error in reading file");</span><br><span class="line"> println!("With text:\n{}", contents)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在以上代码里</p><ol><li>我们新use了一个process库用来退出程序</li><li>我们使用了之前没用过的错误处理函数<code>unwrap_or_else</code>,其大致工作内容也很简单,主要就是捕获错误信息,把报错信息传入闭包<code>err</code>中,作为参数传递进下面的花括号(匿名函数)里,运行花括号的代码。如果没有报错,则自动获取Ok里存储的变量返回。大致就是,和<code>unwrap</code>类似,不过多了一个可以执行额外错误处理代码的功能</li></ol><p>现在我们再来跑一个错误的命令,来看看报错提示</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cargo run </span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 0.00s</span><br><span class="line"> Running `target\debug\minigrep.exe`</span><br><span class="line">Problem parsing args: Not enough arguments</span><br><span class="line">error: process didn't exit successfully: `target\debug\minigrep.exe` (exit code: 1)</span><br></pre></td></tr></table></figure></div><p>完美,提示简短且易懂。</p><p>此外,对于run函数我们也作同样的操作;</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">use std::error::Error;</span><br><span class="line"></span><br><span class="line">fn run(config: Config) -> Result<(), Box<dyn Error>>{</span><br><span class="line"> let contents = fs::read_to_string(config.filename)?;</span><br><span class="line"> println!("With text:\n{}", contents);</span><br><span class="line"> Ok(())</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>对于run,我们使用了一种新的处理方式->问号表达式,之前学过,这可以自动为我们在有错误的时候返回错误,减少代码的编写量;然后我们use了一个新的东西<code>std::error::Error</code>,并且使用了一个叫做<code>Box</code>的trait,指定返回的值是一个<code>Error</code>的trait,同时使用了dyn关键词;这里的主要作用就是,因为问号表达式返回的错误类型不是我们容易知道的,所以使用trait的方法可以方便的,动态的帮我们捕获不同的错误类型并进行返回。最后,因为run函数不需要返回什么值,所以只需要Ok里包含空元组就可以了。</p><p>同样的,最后再修改一下main函数,处理返回的Result即可</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> </span><br><span class="line"> let config = Config::new(&args).unwrap_or_else(|err|{</span><br><span class="line"> println!("Problem parsing args: {}", err);</span><br><span class="line"> process::exit(1);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", config.query);</span><br><span class="line"> println!("In file {}", config.filename);</span><br><span class="line"></span><br><span class="line"> if let Err(e) = run(config){</span><br><span class="line"> println!("Application error: {}", e);</span><br><span class="line"> process::exit(1);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们也使用了一种新的处理方式,也就是在<a href="https://twosix.page/archives/327">6. 枚举与模式匹配</a>里学到的if let语句,用于匹配枚举类型中的其中一种情况。这里因为我们的run函数必定返回一个空元组,所以我们没必要通过unwrap_or_else提取这个空元组,所以简单对Err这种情况进行处理即可。</p><p>当然,语言是很灵活的,我们也可以选择使用match等语句处理我们的报错。</p><h2 id="把代码分离成独立的包"><a href="#把代码分离成独立的包" class="headerlink" title="把代码分离成独立的包"></a>把代码分离成独立的包</h2><p>代码基本已经分离完成了,现在我们只需要把分离的代码放到lib.rs里即可。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">use std::fs;</span><br><span class="line">use std::error::Error;</span><br><span class="line"></span><br><span class="line">pub struct Config{</span><br><span class="line"> pub query: String,</span><br><span class="line"> pub filename: String,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">impl Config {</span><br><span class="line"> pub fn new(args: &Vec<String>) -> Result<Config, &str>{</span><br><span class="line"> if args.len() < 3 {</span><br><span class="line"> return Err("Not enough arguments");</span><br><span class="line"> }</span><br><span class="line"> let query = args[1].clone();</span><br><span class="line"> let filename = args[2].clone();</span><br><span class="line"> Ok(Config{query, filename})</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">pub fn run(config: Config) -> Result<(), Box<dyn Error>>{</span><br><span class="line"> let contents = fs::read_to_string(config.filename)?;</span><br><span class="line"> println!("With text:\n{}", contents);</span><br><span class="line"> Ok(())</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>别忘了声明接口为pub,让外部可以调用~以及相关的use语句也要搬过来。</p><p>搬迁完毕,接下来只要到main.rs里把我们的库use进来即可,或者不用use,直接使用顶层包进行绝对路径访问也没问题,详见<a href="https://twosix.page/archives/332">7. 包、单元包和模块</a></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line">use std::process;</span><br><span class="line"></span><br><span class="line">use minigrep::Config;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> </span><br><span class="line"> let config = Config::new(&args).unwrap_or_else(|err|{</span><br><span class="line"> println!("Problem parsing args: {}", err);</span><br><span class="line"> process::exit(1);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> println!("Searching for {}", config.query);</span><br><span class="line"> println!("In file {}", config.filename);</span><br><span class="line"></span><br><span class="line"> if let Err(e) = minigrep::run(config){</span><br><span class="line"> println!("Application error: {}", e);</span><br><span class="line"> process::exit(1);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里的Config就是通过use导入,run函数因为处于顶层,所以使用我们顶层包名minigrep直接调用也很方便(如果你创建项目的时候名字不是minigrep记得改成你的项目名字)</p><p>运行通过!我们的基础功能就基本完成了,后面再把用于测试用的<code>println</code>语句删除就好。</p><p>好了,以上代码基本结合了我们学习的大部分内容了,用模块化的思路设计完基本功能逻辑完成后,接下来就该结合一下我们测试的知识了</p><h1 id="使用测试驱动开发来编写库功能"><a href="#使用测试驱动开发来编写库功能" class="headerlink" title="使用测试驱动开发来编写库功能"></a>使用测试驱动开发来编写库功能</h1><p>软件主要功能是搜索文件里的字符串,那还差一个搜索的功能。本小节就主要讲的是按照测试驱动开发(TDD)的流程来开发搜索的逻辑。大概是以下步骤</p><ol><li>编写一个必然失败的测试,运行测试,确保它一定失败</li><li>编写或修改刚好足够多的代码,让测试通过</li><li>在保证测试通过的前提下,重构代码</li><li>回到步骤1,进行开发</li></ol><p>这只是其中一种开发技术,主要思想是:优先编写测试,再编写能通过测试的代码,有助于开发过程中保持较高的测试覆盖率。</p><p>接下来,我们就开始第一步</p><h2 id="编写一个会失败的测试"><a href="#编写一个会失败的测试" class="headerlink" title="编写一个会失败的测试"></a>编写一个会失败的测试</h2><p><strong>lib.rs</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">mod test{</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn one_result(){</span><br><span class="line"> let query = "duct";</span><br><span class="line"> let content = "</span><br><span class="line">Rust:</span><br><span class="line">safe, fast, productive.</span><br><span class="line">Pick three.";</span><br><span class="line"> </span><br><span class="line"> assert_eq!(vec!["safe, fast, productive."], search(query, content));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们编写了一个测试模块,给出了测试用的查询字符串,以及内容字符串,再调用了一下实现搜索的功能函数,断言搜索的结果是一个vec数组;</p><p>现在我们还没实现search功能,所以后面就来实现一下。但因为我们首先要编写一个必然失败的测试,所以我们先来试试写一个函数,必然返回空数组,这样就和断言的结果不同,导致失败。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{</span><br><span class="line"> vec![]</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里因为我们选择传入字符串的引用,所以需要使用生命周期,这里返回的值主要存的是contents里的部分内容,所以只需要在contents和返回值里标上生命周期即可,query并不需要</p><p>接下来我们用<code>cargo test</code>运行一下测试吧</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">running 1 test</span><br><span class="line">test test::one_result ... FAILED</span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line"></span><br><span class="line">---- test::one_result stdout ----</span><br><span class="line">thread 'test::one_result' panicked at 'assertion failed: `(left == right)`</span><br><span class="line"> left: `["safe, fast, productive."]`,</span><br><span class="line"> right: `[]`', src\lib.rs:41:9</span><br><span class="line">note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line"> test::one_result</span><br><span class="line"></span><br><span class="line">test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br></pre></td></tr></table></figure></div><p>输出测试失败,左右不相等,一切按我们计划的在走,那接下来就是第二步了,编写或修改代码,让测试通过。</p><h2 id="编写可以通过测试的代码"><a href="#编写可以通过测试的代码" class="headerlink" title="编写可以通过测试的代码"></a>编写可以通过测试的代码</h2><p>接下来我们按照正常的思路实验一下search函数的功能:</p><ol><li>遍历内容的每一行</li><li>搜索行里是否包含搜索的字符串</li><li>如果包含,就把这行添加到列表里;否则忽略</li><li>返回列表</li></ol><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">pub fn search<'a>(query: & str, contents: &'a str) -> Vec<&'a str>{</span><br><span class="line"> let mut ret = Vec::new();</span><br><span class="line"> for eachline in contents.lines() {</span><br><span class="line"> if eachline.contains(query){</span><br><span class="line"> ret.push(eachline);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ret</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>大概解释一下:</p><ol><li>new了一个可变的动态列表<code>ret</code>,用来存返回值</li><li>用for循环遍历contents的每一行,lines()函数可以把字符串分割成若干行,返回一个迭代器</li><li>使用contains函数判断query是否是eachline的子串,如果是,push到ret里</li><li>返回ret</li></ol><p>最后再运行一下测试,不出意外就通过了。</p><h2 id="把search集成到run函数内"><a href="#把search集成到run函数内" class="headerlink" title="把search集成到run函数内"></a>把search集成到run函数内</h2><p>上面已经把所有要写的都写完啦,可以集成功能并试着运行了</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">pub fn run(config: Config) -> Result<(), Box<dyn Error>>{</span><br><span class="line"> let contents = fs::read_to_string(config.filename)?;</span><br><span class="line"> for (i, line) in search(&config.query, &contents).iter().enumerate(){</span><br><span class="line"> println!("{}: {}\n", i, line);</span><br><span class="line"> }</span><br><span class="line"> Ok(())</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>也简单解释一下:</p><ol><li>通过for循环遍历所有的搜索结果</li><li>通过.iter().enumerate()让迭代器生成的结果附带上当前的循环次数</li><li>输出搜索的结果</li></ol><p>接下来就通过我们熟悉的命令行参数运行程序吧:<code>cargo run frog poem.txt</code></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cargo run frog poem.txt</span><br><span class="line"> Compiling minigrep v0.1.0 (E:\Code\rust\minigrep)</span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 0.26s</span><br><span class="line"> Running `target\debug\minigrep.exe frog poem.txt`</span><br><span class="line">0: How public, like a frog</span><br></pre></td></tr></table></figure></div><p>完美。</p><h1 id="处理环境变量"><a href="#处理环境变量" class="headerlink" title="处理环境变量"></a>处理环境变量</h1><p>如果有这么一个参数,例如是否开启不分大小写搜索,那每次用户启动的时候都需要通过命令行来输入一串指令开启就有点太麻烦了。这一部分我们就基于此优化一下我们的代码,主要依赖的就是<strong>环境变量</strong>。</p><p>同样基于TDD的计划流程来编写功能。</p><h2 id="编写一个必然失败的不区分大小写搜索测试"><a href="#编写一个必然失败的不区分大小写搜索测试" class="headerlink" title="编写一个必然失败的不区分大小写搜索测试"></a>编写一个必然失败的不区分大小写搜索测试</h2><p>首先是写测试,就不解释了,主要是实现了两个测试,一个大小写敏感和一个大小写不敏感。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">#[cfg(test)]</span><br><span class="line">mod test{</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn one_result(){</span><br><span class="line"> let query = "duct";</span><br><span class="line"> let content = "</span><br><span class="line">Rust:</span><br><span class="line">safe, fast, productive.</span><br><span class="line">Pick three.";</span><br><span class="line"> </span><br><span class="line"> assert_eq!(vec!["safe, fast, productive."], search(query, content));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn case_sensitive(){</span><br><span class="line"> let query = "duct";</span><br><span class="line"> let content = "</span><br><span class="line">Rust:</span><br><span class="line">safe, fast, productive.</span><br><span class="line">Pick three.</span><br><span class="line">Duct tape.";</span><br><span class="line"></span><br><span class="line"> assert_eq!(vec!["safe, fast, productive."], search(query, content));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn case_insensitive(){</span><br><span class="line"> let query = "rUst";</span><br><span class="line"> let content = "</span><br><span class="line">Rust:</span><br><span class="line">safe, fast, productive.</span><br><span class="line">Pick three.</span><br><span class="line">Trust me.";</span><br><span class="line"> assert_eq!(vec!["Rust:", "Trust me."], search_case_insensitive(query, content));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里省略掉实现search_case_insensitive返回空数组,导致测试失败的部分。可以自己试试,观察是否确保测试失败。</p><h2 id="编写能通过测试的代码"><a href="#编写能通过测试的代码" class="headerlink" title="编写能通过测试的代码"></a>编写能通过测试的代码</h2><p>然后是实现大小写不敏感的搜索功能函数,所谓大小写不敏感,其实就是把所有字母转成小写/大小再来作比较。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{</span><br><span class="line"> let mut ret = Vec::new();</span><br><span class="line"> let query = query.to_lowercase();</span><br><span class="line"> for eachline in contents.lines() {</span><br><span class="line"> if eachline.to_lowercase().contains(&query){</span><br><span class="line"> ret.push(eachline);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ret</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>以上代码需要注意的是:因为<code>to_lowercase</code>函数把字符串原来的数据更改了,所以会创建一个新的字符串存储新的数据,所以这里我们的query变成了拥有自己所有权的String变量,而不再是原来的字符串切片,所以后续传入contains时又使用了&来借用。</p><p>运行测试通过的话,就没有问题了。</p><h2 id="把功能搬迁到run函数内"><a href="#把功能搬迁到run函数内" class="headerlink" title="把功能搬迁到run函数内"></a>把功能搬迁到run函数内</h2><p>因为用户自行决定是否开启大小写敏感的功能,所以要新增一个配置项来存储这个结果,所以我们修改一个Config结构</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pub struct Config{</span><br><span class="line"> pub query: String,</span><br><span class="line"> pub filename: String,</span><br><span class="line"> pub is_sensitive: bool,</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>接下来,再结合is_sensitive把search_case_insensitive的功能搬到run函数里就差不多了</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">pub fn run(config: Config) -> Result<(), Box<dyn Error>>{</span><br><span class="line"> let contents = fs::read_to_string(config.filename)?;</span><br><span class="line"> let result = if config.is_sensitive{</span><br><span class="line"> search(&config.query, &contents)</span><br><span class="line"> }</span><br><span class="line"> else{</span><br><span class="line"> search_case_insensitive(&config.query, &contents)</span><br><span class="line"> };</span><br><span class="line"> for (i, line) in result.iter().enumerate(){</span><br><span class="line"> println!("{}: {}\n", i, line);</span><br><span class="line"> }</span><br><span class="line"> Ok(())</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>不过这段代码还不能编译通过,因为我们还没修改Config的new函数。这里因为我们不希望通过之前的命令行参数解析的方法来得到is_sensitive的值,所以我们要使用新的方法——读取环境变量。</p><p>环境变量可以让一个参数的值再整个会话内都有效,就不需要每次运行都输入一遍了,很方便。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">impl Config {</span><br><span class="line"> pub fn new(args: &Vec<String>) -> Result<Config, &str>{</span><br><span class="line"> if args.len() < 3 {</span><br><span class="line"> return Err("Not enough arguments");</span><br><span class="line"> }</span><br><span class="line"> let query = args[1].clone();</span><br><span class="line"> let filename = args[2].clone();</span><br><span class="line"> let is_sensitive = !(env::var("SENSITIVE").is_err());</span><br><span class="line"> Ok(Config{query, filename, is_sensitive})</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol><li>我们新use了env,用来读取环境变量</li><li>添加了新的一行,通过env的var函数,读取名为“SENSITIVE”的环境变量</li><li>用Result的is_err()来处理报错,因为我们这里的is_sensitive就是一个bool值,is_err也是返回一个布尔值,正确读取环境变量的时候返回False,读取失败的时候返回True,用<code>!</code>取反之后整好对上我们想实现的逻辑,也就是设置了环境变量的时候,则为True大小写敏感,否则False大小写不敏感。因为我们不需要关注环境变量的值,只想知道有没有。</li></ol><p>写完之后,我们就来运行一下看看吧</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">cargo run to poem.txt</span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 0.00s</span><br><span class="line"> Running `target\debug\minigrep.exe to poem.txt`</span><br><span class="line">0: Are you nobody, too?</span><br><span class="line"></span><br><span class="line">1: How dreary to be somebody!</span><br><span class="line"></span><br><span class="line">2: To tell your name the livelong day</span><br><span class="line"></span><br><span class="line">3: To an admiring bog!</span><br></pre></td></tr></table></figure></div><p>我们在poem.txt里搜索to,结果没有问题,大小写不敏感,搜索出了to和To的结果</p><p>接下来,我们设置一下环境变量,这对于每个系统方法都不一样,这里只说一下windows的方法,在终端里输入以下语句</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$env:SENSITIVE=1</span><br></pre></td></tr></table></figure></div><p>然后再运行代码</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">cargo run to poem.txt </span><br><span class="line"> Compiling minigrep v0.1.0 (E:\Code\rust\minigrep)</span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 0.41s</span><br><span class="line"> Running `target\debug\minigrep.exe to poem.txt`</span><br><span class="line">0: Are you nobody, too?</span><br><span class="line"></span><br><span class="line">1: How dreary to be somebody!</span><br></pre></td></tr></table></figure></div><p>也没有问题,只搜索出小写的to结果</p><p>现在,这一部分也完成了。</p><h1 id="将错误提示信息打印到标准错误而不是标准输出"><a href="#将错误提示信息打印到标准错误而不是标准输出" class="headerlink" title="将错误提示信息打印到标准错误而不是标准输出"></a>将错误提示信息打印到标准错误而不是标准输出</h1><p>println是输出到标准输出流,也就是打印到屏幕上,另一种输出流是标准错误流,会将正常的输出保存到文件里,错误的输出依旧打印到屏幕上。</p><p>我觉得这部分不重要,就简单放代码了。</p><p>把<code>println</code>改成<code>eprintln</code>即可输出到标准错误</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">use std::env;</span><br><span class="line">use std::process;</span><br><span class="line"></span><br><span class="line">use minigrep::Config;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let args: Vec<String> = env::args().collect();</span><br><span class="line"> </span><br><span class="line"> let config = Config::new(&args).unwrap_or_else(|err|{</span><br><span class="line"> eprintln!("Problem parsing arguments: {}", err);</span><br><span class="line"> process::exit(1);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> if let Err(e) = minigrep::run(config){</span><br><span class="line"> eprintln!("Application error: {}", e);</span><br><span class="line"> process::exit(1);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>运行错误指令,并指定输出文件,查看输出</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cargo run > output.txt</span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 0.00s</span><br><span class="line"> Running `target\debug\minigrep.exe`</span><br><span class="line">Problem parsing arguments: Not enough arguments</span><br><span class="line">error: process didn't exit successfully: `target\debug\minigrep.exe` (exit code: 1)</span><br></pre></td></tr></table></figure></div><p>可以看见错误信息打印到了屏幕上,而output.txt没有内容</p><p>运行正确指令,查看输出</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cargo run to poem.txt > output.txt</span><br><span class="line"> Finished dev [unoptimized + debuginfo] target(s) in 0.00s</span><br><span class="line"> Running `target\debug\minigrep.exe to poem.txt</span><br></pre></td></tr></table></figure></div><p>可以发现终端没有任何内容输出,输出搜索结果都在output.txt里</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这章主要还是复习以前的内容吧,大概就是</p><ol><li>结合基本的概念,如所有权,生命周期等编写基本逻辑</li><li>如何使用函数,结构体,模块等功能结构化管理程序逻辑</li><li>如何捕获并正确的处理程序错误</li><li>如何编写测试模块</li><li>一些乱七八糟的命令行处理等</li></ol>]]></content>
<summary type="html"><blockquote>
<p>本章节我们将开始学习编写一个小项目——开发一个能够和文件系统交互并处理命令行输入、输出的工具</p>
</blockquote>
<h1 id="基本功能实现"><a href="#基本功能实现" class="headerlink" title=</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】11. 编写自动化测试</title>
<link href="https://twosix.page/2023/05/09/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9111-%E7%BC%96%E5%86%99%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/"/>
<id>https://twosix.page/2023/05/09/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9111-%E7%BC%96%E5%86%99%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/</id>
<published>2023-05-09T08:00:30.000Z</published>
<updated>2024-07-04T15:52:28.391Z</updated>
<content type="html"><![CDATA[<blockquote><p>这一章讲的就是怎么在Rust编写单元测试代码,这一部分的思想不仅适用于Rust,在绝大多数语言都是有用武之地的</p></blockquote><h1 id="如何编写测试"><a href="#如何编写测试" class="headerlink" title="如何编写测试"></a>如何编写测试</h1><h2 id="测试代码的构成"><a href="#测试代码的构成" class="headerlink" title="测试代码的构成"></a>测试代码的构成</h2><h3 id="构成"><a href="#构成" class="headerlink" title="构成"></a>构成</h3><p>通用测试代码通常包括三个部分</p><ol><li>准备所需的数据或者前置状态</li><li>调用需要测试的代码</li><li>使用断言,判断运行结果是否和我们期望的一致</li></ol><p>在Rust中,有专门用于编写测试代码的相关功能,包含test属性,测试宏,should_panic属性等等</p><p>在最简单的情况下,Rust中的测试就是一个标注有<code>test</code>属性的函数。只需要将<code>#[test]</code>添加到函数的关键字<code>fn</code>上,就能使用<code>cargo test</code>命令来运行测试。</p><p>测试命令会构建一个可执行文件,调用所有标注了test的函数,生成相关报告。</p><blockquote><p>PS:</p><p><strong>属性</strong>是一种修饰代码的一种元数据,例如之前为了输出结构体时,加入的<code>#[derive(Debug)]</code>就是一个属性,声明属性后,会为下面的代码自动生成一些实现,如<code>#[derive(Debug)]</code>修饰结构体时,就会为结构体生成Debug trait的实现</p></blockquote><h3 id="初次尝试"><a href="#初次尝试" class="headerlink" title="初次尝试"></a>初次尝试</h3><p>接下来我们就试试怎么测试</p><p>首先新建一个名为adder的项目<code>cargo new adder --lib</code>(–lib指生成lib.rs文件)</p><p>可能是版本比较新,lib.rs里直接生成有了以下代码</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">pub fn add(left: usize, right: usize) -> usize {</span><br><span class="line"> left + right</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn it_works() {</span><br><span class="line"> let result = add(2, 2);</span><br><span class="line"> assert_eq!(result, 4);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>我们先忽略一些没讲过的关键词,这段代码里我们定义了一个<code>it_works</code>函数,并标注为测试函数,然后使用断言判断<code>add</code>函数的结果是否正确的等于4。在了解了大概功能之后,我们直接运行测试看看</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">cargo test</span><br><span class="line"> Compiling adder v0.1.0 (E:\Code\rust\adder)</span><br><span class="line"> Finished test [unoptimized + debuginfo] target(s) in 0.29s</span><br><span class="line"> Running unittests src\lib.rs (target\debug\deps\adder-0a86cd050490705e.exe)</span><br><span class="line"></span><br><span class="line">running 1 test</span><br><span class="line">test tests::it_works ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br><span class="line"></span><br><span class="line"> Doc-tests adder</span><br><span class="line"></span><br><span class="line">running 0 tests</span><br><span class="line"></span><br><span class="line">test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br></pre></td></tr></table></figure></div><p>对应的测试结果如上</p><ul><li>passed: 测试通过的函数数量,我们这里只有一个it_works函数,且测试通过,所以为1</li><li>failed: 测试失败的函数数量</li><li>ignored: 被标记为忽略的测试函数,后面会提</li><li>measured: Rust还提供了衡量函数性能的benchmark方法,不过编写书的时候似乎这部分还不完善,所以不会有讲解,想了解需要自行学习</li><li>filtered out:被过滤掉的测试函数</li><li>Doc-tests:文档测试,这是个很好用的特性,可以防止你在修改了函数之后,忘记修改自己的文档,保证文档能和实际代码同步。</li></ul><p>测试时,每一个测试函数都是运行在独立的线程里的,所以发生panic时并不会影响其他的测试,我们可以写一个错误的函数看看</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">pub fn add(left: usize, right: usize) -> usize {</span><br><span class="line"> left + right</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn error() {</span><br><span class="line"> let result = add(3, 2);</span><br><span class="line"> assert_eq!(result, 4);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn it_works() {</span><br><span class="line"> let result = add(2, 2);</span><br><span class="line"> assert_eq!(result, 4);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>输出结果:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">cargo test</span><br><span class="line"> Compiling adder v0.1.0 (E:\Code\rust\adder)</span><br><span class="line"> Finished test [unoptimized + debuginfo] target(s) in 0.24s</span><br><span class="line"> Running unittests src\lib.rs (target\debug\deps\adder-0a86cd050490705e.exe)</span><br><span class="line"></span><br><span class="line">running 2 tests</span><br><span class="line">test tests::it_works ... ok</span><br><span class="line">test tests::error ... FAILED</span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line"></span><br><span class="line">---- tests::error stdout ----</span><br><span class="line">thread 'tests::error' panicked at 'assertion failed: `(left == right)`</span><br><span class="line"> left: `5`,</span><br><span class="line"> right: `4`', src\lib.rs:18:9</span><br><span class="line">note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line"> tests::error</span><br><span class="line"></span><br><span class="line">test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br><span class="line"></span><br><span class="line">error: test failed, to rerun pass `--lib`</span><br></pre></td></tr></table></figure></div><p>可以看见,error的panic并不影响it_works的测试通过。</p><h2 id="assert-宏"><a href="#assert-宏" class="headerlink" title="assert!宏"></a>assert!宏</h2><h3 id="assert"><a href="#assert" class="headerlink" title="assert!"></a>assert!</h3><p><code>assert!</code>宏主要的功能是用来确保某个值为<code>true</code>,所以常被用于测试中。如<code>a>b</code>等场景,返回的是一个bool值,就完美的符合assert!的使用场景,可以使用assert!进行测试,例如</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">pub fn cmp(a: i32, b: i32) -> bool {</span><br><span class="line"> if a>b{</span><br><span class="line"> true</span><br><span class="line"> }</span><br><span class="line"> else{</span><br><span class="line"> false</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn cmp_test(){</span><br><span class="line"> let result = cmp(3, 2);</span><br><span class="line"> assert!(result);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="assert-eq-和assert-ne"><a href="#assert-eq-和assert-ne" class="headerlink" title="assert_eq!和assert_ne!"></a>assert_eq!和assert_ne!</h3><p>那如果返回值不是bool值呢?前面也出现过了,我们可以使用<code>assert_eq!</code>或者<code>assert_ne!</code>来断言两个值是否相等。</p><p>eq则对应的只有相等才能通过断言,ne则对应的只有不相等才能通过断言,用例见上面的add测试即可。</p><p><strong>但是注意</strong>,assert_eq!和assert_ne!使用了<code>==</code>和<code>!=</code>来实现是否相等的判断,也就意味着,传入这两个宏的参数是必须实现了<code>PartialEq</code>这个trait的。同时,我们可见错误的输出中会打印出详细的不相等原因,也就是说它还同时需要实现了<code>Debug</code>宏帮助打印输出。一般绝大部分参数都是满足要求的,自定义的结构体时需要注意。</p><blockquote><p>之前提到过属性这个概念,会为你自动实现一些功能,实际上PartialEq和Debug作为可派生的宏,也内置了属性的实现,你只需要在自己定义的结构体上加上<code>#[derive(PartialEq, Debug)]</code>,就能自动帮你实现这两个宏</p></blockquote><h3 id="自定义错误提示代码"><a href="#自定义错误提示代码" class="headerlink" title="自定义错误提示代码"></a>自定义错误提示代码</h3><p>上面我们说到assert_eq是会有详细输出的,告诉你怎么不相等了,帮助你排除bug,但普通的assert!只判断布尔值,所以没办法有详细的输出,这时候我们可以定制一个输出,使得错误提示更人性化一点。</p><p>如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">pub fn cmp(a: i32, b: i32) -> bool {</span><br><span class="line"> if a>b{</span><br><span class="line"> true</span><br><span class="line"> }</span><br><span class="line"> else{</span><br><span class="line"> false</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn cmp_test(){</span><br><span class="line"> let a = 2;</span><br><span class="line"> let b = 3;</span><br><span class="line"> let result = cmp(a, b);</span><br><span class="line"> assert!(result, "{} is not bigger than {}", a, b);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>和一般不一样,我们不需要用什么格式化字符串的方法先格式化一个字符串,再传入这个字符串,断言支持直接使用格式化的语法。这段代码的输出如下</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">running 1 test</span><br><span class="line">thread 'tests::cmp_test' panicked at '2 is not bigger than 3', src\lib.rs:23:9</span><br><span class="line">stack backtrace:</span><br></pre></td></tr></table></figure></div><p>可见报错提示相对于单纯的panicked at更人性化了一些。</p><p>当然,自定义输出也支持在assert_eq和assert_ne里使用</p><h2 id="should-panic"><a href="#should-panic" class="headerlink" title="should_panic"></a>should_panic</h2><p>should_panic也是一个属性,用来测试代码是否能正确的在出错时发生panic。用例如下</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">pub fn positive_num(a: i32) -> i32 {</span><br><span class="line"> if a > 0 {</span><br><span class="line"> a</span><br><span class="line"> } else {</span><br><span class="line"> panic!("{} is not positive", a)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> #[should_panic]</span><br><span class="line"> fn pos_test(){</span><br><span class="line"> let a = -1;</span><br><span class="line"> positive_num(a);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码用来检查一个数是否是正数,在不是时抛出panic,接下来我们使用<code>#[should_panic]</code>来检查程序是否正确的panic,这段代码运行测试通过没问题。</p><p>接下来我们修改一下a的值,让程序不抛出panic,看看会发生什么</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">running 1 test</span><br><span class="line">test tests::pos_test - should panic ... FAILED</span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line"></span><br><span class="line">---- tests::pos_test stdout ----</span><br><span class="line">note: test did not panic as expected</span><br><span class="line"></span><br><span class="line">failures:</span><br><span class="line"> tests::pos_test</span><br><span class="line"></span><br><span class="line">test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br><span class="line"></span><br><span class="line">error: test failed, to rerun pass `-p adder --lib`</span><br></pre></td></tr></table></figure></div><p>测试失败,告诉你pos_test没有按照预期发生panic。这个特性可以用来检查你的代码是否能正确的处理报错,发生panic以阻止程序进一步运行,产生不可预估的后果。</p><p>但是,单纯这么使用感觉有点含糊不清,因为程序发生panic的原因可能不是我们所预期的,假如其他一些我们不知道的原因抛出了panic,也会导致测试通过。所以我们可以添加一个可选参数<code>expected</code>,用来检查panic发生报错的输出信息里是否包含指定的文字。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">#[test]</span><br><span class="line">#[should_panic(expected = "positive")]</span><br><span class="line">fn pos_test(){</span><br><span class="line"> let a = -1;</span><br><span class="line"> positive_num(a);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这时候,should_panic就会检查发生的panic输出的报错信息是否包含”positive”这个字符串,如果是,才会测试通过,输出如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">running 1 test</span><br><span class="line">thread 'tests::pos_test' panicked at '-1 is not positive', src\lib.rs:9:9</span><br><span class="line">stack backtrace:</span><br></pre></td></tr></table></figure></div><p>可见,输出中也包含了报错的信息,更人性化了。</p><h2 id="使用Result编写测试"><a href="#使用Result编写测试" class="headerlink" title="使用Result编写测试"></a>使用Result编写测试</h2><p>之前学习Result枚举的时候我们就知道了这东西是用来处理报错的,自然也就可以用来处理测试。使用时也很简单,我们只需要声明测试函数的返回值是Result,test命令就会自动根据Result的枚举结果来判断是否测试成功了。如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn it_works() -> Result<(), String>{</span><br><span class="line"> if 2+2 == 4 {</span><br><span class="line"> Ok(())</span><br><span class="line"> } else {</span><br><span class="line"> Err(String::from("two plus two does not equal four"))</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>使用Result编写测试函数的主要优势是可以使用问号表达式进行错误捕获,更方便我们去编写一些复杂的测试函数,可以让函数在任一时刻有错误被捕获到时,就返回报错。</p><p>问号表达式的使用可见<a href="https://twosix.page/archives/345">【Rust 学习记录】9. 错误处理</a>的?运算符部分,这里就不再写代码举例了(主要书上没例子,我也懒的写)</p><h1 id="控制测试的运行方式"><a href="#控制测试的运行方式" class="headerlink" title="控制测试的运行方式"></a>控制测试的运行方式</h1><p>这一部分主要是对cargo test命令的讲解,具体的运行方式,参数的使用等。</p><p>cargo test的参数统一需要写在<code>--</code>后面,也就是说你想要使用<code>--help</code>显示参数文档时,需要使用以下命令</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo test -- --help</span><br></pre></td></tr></table></figure></div><h2 id="并行或串行的执行代码"><a href="#并行或串行的执行代码" class="headerlink" title="并行或串行的执行代码"></a>并行或串行的执行代码</h2><p>默认情况下,测试是多线程并发执行的,这可以使测试更快速的完成,且相互之间不会影响结果。但如果测试间有相互依赖关系,则需要串行执行。例如两个测试用例同时在操作一个文件,一个测试在写内容,一个测试在读内容时,则容易导致测试结果不合预期。</p><p>我们可以使用<code>--test-threads=1</code>来指定测试的线程数为1,即可实现串行执行,当然,你想执行的更快也可以指定更多的线程</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo test -- --test-threads=1</span><br></pre></td></tr></table></figure></div><h2 id="显示函数的输出"><a href="#显示函数的输出" class="headerlink" title="显示函数的输出"></a>显示函数的输出</h2><p>默认情况下,test命令会捕获所有测试成功时的输出,也就是说,对于测试成功的函数,即使你使用了println!打印输出,你也无法在控制台看见你的输出,因为它被test命令捕获吞掉了。</p><p>如果你想要在控制台显示你的输出,只需要用<code>--nocapture</code>设置不捕获输出即可</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo test -- --nocapture</span><br></pre></td></tr></table></figure></div><h2 id="只运行部分特定名称的测试"><a href="#只运行部分特定名称的测试" class="headerlink" title="只运行部分特定名称的测试"></a>只运行部分特定名称的测试</h2><p>如果测试的函数越写越多,执行所有的测试可能很花时间,通常我们编写了一个新的功能并想进行测试的时候,我们只需要测试这一个功能就足够了,因此可以向test命令指定函数名称来进行测试。</p><p>如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">fn add_two(a: i32, b: i32) -> i32 {</span><br><span class="line"> a + b</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn add_test_1() {</span><br><span class="line"> assert_eq!(add_two(1, 2), 3);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn add_test_2() {</span><br><span class="line"> assert_eq!(add_two(2, 2), 4);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn one_hundred() {</span><br><span class="line"> assert_eq!(100, 100);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>对于这段代码,我们只想测试<code>one_hundred</code>这个函数,只需要对test命令指定运行one_hundred即可</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo test one_hundred</span><br></pre></td></tr></table></figure></div><p>输出如下:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">running 1 test</span><br><span class="line">test tests::one_hundred ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s</span><br></pre></td></tr></table></figure></div><p>这里显示<code>2 filtered out</code>,代表有两个测试用例被我们过滤掉了。</p><p>当然,这个方法也并不是只能运行一个测试函数,也可以通过部分匹配的方法执行多个名称里包含相同字符串的测试函数,例如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">cargo test add </span><br><span class="line"></span><br><span class="line">running 2 tests</span><br><span class="line">test tests::add_test_1 ... ok</span><br><span class="line">test tests::add_test_2 ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s</span><br></pre></td></tr></table></figure></div><p>我们使用<code>cargo test add</code>命令,则可以测试所有名字里带<code>add</code>的测试函数,忽略掉了one_hundred函数。</p><p>不过需要注意的是,这种方法一次只能使用一个参数进行匹配并测试,如果你想同时用多个规则匹配多类的测试函数,就需要用其他方法了。</p><h2 id="通过显示指定来忽略某些测试"><a href="#通过显示指定来忽略某些测试" class="headerlink" title="通过显示指定来忽略某些测试"></a>通过显示指定来忽略某些测试</h2><h3 id="忽略部分测试函数"><a href="#忽略部分测试函数" class="headerlink" title="忽略部分测试函数"></a>忽略部分测试函数</h3><p>当有部分测试函数执行特别耗时时,我们不想每次测试都执行这个函数,我们就可以通过<code>#[ignore]</code>属性来显示指定忽略这个测试函数。如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">fn add_two(a: i32, b: i32) -> i32 {</span><br><span class="line"> a + b</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn add_test_1() {</span><br><span class="line"> assert_eq!(add_two(1, 2), 3);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> #[ignore]</span><br><span class="line"> fn add_test_2() {</span><br><span class="line"> assert_eq!(add_two(2, 2), 4);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn one_hundred() {</span><br><span class="line"> assert_eq!(100, 100);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>此时我们直接执行<code>cargo test</code>,输出如下</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">running 3 tests</span><br><span class="line">test tests::add_test_2 ... ignored</span><br><span class="line">test tests::add_test_1 ... ok</span><br><span class="line">test tests::one_hundred ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br><span class="line"></span><br><span class="line"> Doc-tests adder</span><br></pre></td></tr></table></figure></div><p>可见<code>add_test_2</code>函数被忽略不执行了,提示1 ignored。</p><h3 id="单独执行被忽略的测试函数"><a href="#单独执行被忽略的测试函数" class="headerlink" title="单独执行被忽略的测试函数"></a>单独执行被忽略的测试函数</h3><p>如果我们想单独执行这些被忽略的函数,则可以使用<code>--ignored</code>命令</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">cargo test -- --ignored</span><br><span class="line"></span><br><span class="line">running 1 test</span><br><span class="line">test tests::add_test_2 ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s</span><br></pre></td></tr></table></figure></div><p>可见,只有<code>add_test_2</code>被执行了。</p><h1 id="测试的组织结构"><a href="#测试的组织结构" class="headerlink" title="测试的组织结构"></a>测试的组织结构</h1><p>测试通常分为两类,单元测试和集成测试。单元测试小而专注,集中于测试一个私有接口或模块;集成测试则独立于代码库之外,正常的从外部调用公共接口,一次测试可能使用多个模块</p><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><p>单元测试的目的在于将一小段代码单独隔离开来,快速确定代码结果是否符合预期。一般来说,单元测试的代码<strong>和需要测试的代码存放在同一文件中</strong>。同时也约定俗成的在每个源代码文件里都会新建一个tests模块来存放测试函数,并使用cfg(test)来标注。</p><h3 id="测试模块和-cfg-test"><a href="#测试模块和-cfg-test" class="headerlink" title="测试模块和#[cfg(test)]"></a>测试模块和#[cfg(test)]</h3><p><code>#[cfg(test)]</code>旨在让Rust只在执行Cargo test命令的时候编译和运行这段代码,而在cargo build的时候剔除掉它们,只用于测试,节省编译时间与空间,使得我们可以更方便的把测试代码和源代码放在同一个文件里。(集成测试时一般不需要标注,因为集成测试一般是独立的一个文件)</p><p>我们之前编写的测试模块就使用了这个属性</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn it_works() {</span><br><span class="line"> let result = add(2, 2);</span><br><span class="line"> assert_eq!(result, 4);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="测试私有函数"><a href="#测试私有函数" class="headerlink" title="测试私有函数"></a>测试私有函数</h3><p>是不是应该测试私有函数一直有争议,不管你觉得要不要,但Rust提供了方法供你方便的测试。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">fn add(left: usize, right: usize) -> usize {</span><br><span class="line"> left + right</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#[cfg(test)]</span><br><span class="line">mod tests {</span><br><span class="line"> use super::*;</span><br><span class="line"></span><br><span class="line"> #[test]</span><br><span class="line"> fn it_works() {</span><br><span class="line"> let result = add(2, 2);</span><br><span class="line"> assert_eq!(result, 4);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>以上代码中的add没有标注pub关键字,也就是私有的,但因为Rust的测试代码本身也属于Rust代码,所以可以通过use的方法把私有的函数引入当前作用域来测试,也就是对应的代码里的<code>use super::*;</code></p><h2 id="集成测试"><a href="#集成测试" class="headerlink" title="集成测试"></a>集成测试</h2><p>集成测试通常是新建一个tests目录,只调用对外公开的那部分接口。</p><h3 id="tests目录"><a href="#tests目录" class="headerlink" title="tests目录"></a>tests目录</h3><p>tests目录需要和src文件夹并列,Cargo会自动在这个目录下面寻找测试文件。</p><p>现在,我们新建一个<code>tests/integration_test.rs</code>文件,保留之前的lib.rs代码(add函数如果改了私有记得改回公有),并编写测试代码。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">use adder;</span><br><span class="line"></span><br><span class="line">#[test]</span><br><span class="line">fn add_two() {</span><br><span class="line"> assert_eq!(adder::add(2, 2), 4);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>集成测试就不需要<code>#[cfg(test)]</code>了,Rust有单独为tests目录做处理,不会build这个目录下的文件。</p><p>接下来,我们再执行以下Cargo test,看看输出</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">cargo test</span><br><span class="line"> Compiling adder v0.1.0 (E:\Code\rust\adder)</span><br><span class="line"> Finished test [unoptimized + debuginfo] target(s) in 0.32s</span><br><span class="line"> Running unittests src\lib.rs (target\debug\deps\adder-0a86cd050490705e.exe)</span><br><span class="line"></span><br><span class="line">running 1 test</span><br><span class="line">test tests::it_works ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br><span class="line"></span><br><span class="line"> Running tests\integration_test.rs (target\debug\deps\integration_test-57f19c149db40d76.exe)</span><br><span class="line"></span><br><span class="line">running 1 test</span><br><span class="line">test add_two ... ok</span><br><span class="line"></span><br><span class="line">test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br><span class="line"></span><br><span class="line"> Doc-tests adder</span><br><span class="line"></span><br><span class="line">running 0 tests</span><br><span class="line"></span><br><span class="line">test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br></pre></td></tr></table></figure></div><p>我们可以看到输出里</p><ol><li>先输出了单元测试的结果,<code>test tests::it_works … ok</code>,每行输出一个单元测试结果</li><li>再输出集成测试的结果,<code>Running tests\integration_test.rs</code>,表示正在测试哪个文件的测试模块,后续跟着这个文件的测试结果</li><li>最后是文档测试</li></ol><p>当编写的测试代码越多,输出也就会越多越杂,所以我们也可以使用<code>--test</code>参数指定集成测试的文件名,单独进行测试。如:<code>cargo test --test integration_test</code></p><h3 id="在集成测试中使用子模块"><a href="#在集成测试中使用子模块" class="headerlink" title="在集成测试中使用子模块"></a>在集成测试中使用子模块</h3><p>测试模块也和普通模块差不多,可以把函数分解到不同文件不同子目录里,当我们需要测试内容越来越多的时候,就会需要这么做。</p><p>但因为测试的特殊性,rust会把每个集成测试的文件编译成独立的包来隔离作用域,模拟用户实际的使用环境,这就意味着我们以前在src目录下管理文件的方法并不完全适用于tests目录了。</p><p>例如,我们需要编写一个common.rs文件,并且编写一个setup函数,这个函数将会用在多个不同的测试文件里使用,如</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pub fn setup(){</span><br><span class="line"> // 一些测试所需要初始化的数据</span><br><span class="line"> a = 1;</span><br><span class="line"> a</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>我们执行cargo test时会有以下输出:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> Running tests\common.rs (target\debug\deps\common-15055e88a26e37ec.exe)</span><br><span class="line"></span><br><span class="line">running 0 tests</span><br><span class="line"></span><br><span class="line">test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s</span><br></pre></td></tr></table></figure></div><p>可以发现,即便我们没有在common.rs里写任何测试函数,它依旧会将它作为测试文件执行,并输出无意义的running 0 tests。这明显不是我们所希望的,那如何解决呢?</p><p>我们可以使用mod.rs文件,把common.rs的文件内容移到tests/common/mod.rs里面,这样的意思是让Rust把common视作一个模块,而不是集成测试文件。</p><p>于是,我们就可以通过mod关键字引入common模块并使用其中的函数,例如</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// tests/integration_test.rs</span><br><span class="line">use adder;</span><br><span class="line"></span><br><span class="line">mod common;</span><br><span class="line"></span><br><span class="line">#[test]</span><br><span class="line">fn add_two() {</span><br><span class="line"> common::setup();</span><br><span class="line"> assert_eq!(adder::add(2, 2), 4);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>此时再运行cargo test,就不会出现common相关的测试输出了。</p><h3 id="二进制包的集成测试"><a href="#二进制包的集成测试" class="headerlink" title="二进制包的集成测试"></a>二进制包的集成测试</h3><p>如果我们的项目只有src/main.rs而没有src/lib.rs的话,是没有办法在tests中进行集成测试的,因为只有把代码用lib.rs文件指定为一个代码包crate,才能把函数暴露给其他包来使用,而main.rs对应的是二进制包,只能单独执行自己。</p><p>所以Rust的二进制项目通常会把逻辑编写在src/lib.rs里,main.rs只对lib.rs的内容进行简单的调用。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>没什么好总结的,下一章写项目去了。</p>]]></content>
<summary type="html"><blockquote>
<p>这一章讲的就是怎么在Rust编写单元测试代码,这一部分的思想不仅适用于Rust,在绝大多数语言都是有用武之地的</p>
</blockquote>
<h1 id="如何编写测试"><a href="#如何编写测试" class="headerlin</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】10. 泛型、trait与生命周期</title>
<link href="https://twosix.page/2023/05/08/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9110-%E6%B3%9B%E5%9E%8B%E3%80%81trait%E4%B8%8E%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"/>
<id>https://twosix.page/2023/05/08/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%9110-%E6%B3%9B%E5%9E%8B%E3%80%81trait%E4%B8%8E%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/</id>
<published>2023-05-08T13:17:12.000Z</published>
<updated>2024-07-04T15:52:28.391Z</updated>
<content type="html"><![CDATA[<h1 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h1><p>泛型是一种具体类型或者其他属性的抽象替代,通常用来减少代码的重复,接下来将从泛型的几个实际应用场景开始介绍泛型</p><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><h3 id="在函数定义中使用"><a href="#在函数定义中使用" class="headerlink" title="在函数定义中使用"></a>在函数定义中使用</h3><p>现在假设我们要写一个寻找数组最大值的功能,我要怎么实现既能从字符数组里查找最大值,又能从整数数组里查找最大值?定义两个函数分别查找的话难免重复性有点高,这时候就需要使用泛型。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">largest</span><T>(list: &<T>) <span class="punctuation">-></span> T{</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">largest</span> = list[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">for</span> &item <span class="keyword">in</span> list.<span class="title function_ invoke__">iter</span>() {</span><br><span class="line"> <span class="keyword">if</span> item > largest {</span><br><span class="line"> largest = item;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> largest</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>以上代码定义了一个寻找数组最大值的泛型函数,首先</p><ol><li>需要声明一个泛型名称<code>T</code>,放置在函数名和参数的圆括号之间,用尖括号括起来,<code>largest(list: &<T>)</code></li><li>后续的类型声明,就都可以用<code>T</code>来代替了</li></ol><p>但以上代码暂时无法提示,rust-analyzer会报错<code>binary operation '>' cannoy be applied to type 'T'</code>,也就是说<code>></code>运算符不能直接用于泛型参数,这个问题会在后续解决,现在重点先放在泛型的应用场景。</p><h3 id="在结构体定义中使用"><a href="#在结构体定义中使用" class="headerlink" title="在结构体定义中使用"></a>在结构体定义中使用</h3><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[derive(Debug)]</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Point</span><T> {</span><br><span class="line"> x: T,</span><br><span class="line"> y: T</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">p1</span> = Point{ x: <span class="number">5</span>, y: <span class="number">10</span> };</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">p2</span> = Point{ x: <span class="number">1.0</span>, y: <span class="number">4.0</span> };</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, p1);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, p2);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>结构体中,泛型名称声明在结构体名字后面,<code>Point</code>,这段代码是可以编译通过的。</p><p>但注意,当你使用两种类型的变量创建泛型结构体时,就无法编译通过了。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[derive(Debug)]</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Point</span><T> {</span><br><span class="line"> x: T,</span><br><span class="line"> y: T</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">p1</span> = Point{ x: <span class="number">5</span>, y: <span class="number">10.0</span> };</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, p1);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>报错:<code>expected integer, found floating-point number</code></p><p>这是因为,当你向泛型<code>T</code>传入第一次传入值5的时候,编译器会自动为<code>T</code>赋值为和5相同的类型,即整型。也就是说,<strong>泛型并不是代表能接受所有类型的变量,而是编译器自动帮你识别为第一次接收到的变量类型</strong>。</p><p>但如果我们就是可能传入两个类型呢?解决这个问题也简单,我们声明两个泛型,存储两个类型即可</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[derive(Debug)]</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Point</span><T, U> {</span><br><span class="line"> x: T,</span><br><span class="line"> y: U</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">p1</span> = Point{ x: <span class="number">5</span>, y: <span class="number">10.0</span> };</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{:?}"</span>, p1);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>声明多个泛型只需要在尖括号内用逗号隔开即可。</p><p>这段代码里,我们声明了两个泛型名称<code>T, U</code>,这时候我们分别为类型为<code>T, U</code>的变量<code>x, y</code>传入5,10.0,对应的,此时<code>T</code>代表整型,<code>U</code>代表浮点型</p><h3 id="在方法定义中使用"><a href="#在方法定义中使用" class="headerlink" title="在方法定义中使用"></a>在方法定义中使用</h3><p>有了泛型的结构体,自然也就能有泛型的结构体方法了</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Point</span><T> {</span><br><span class="line"> x: T,</span><br><span class="line"> y: T</span><br><span class="line">}</span><br><span class="line"><span class="keyword">impl</span><T> Point<T> {</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">x</span>(&<span class="keyword">self</span>, other: &Point<T>) <span class="punctuation">-></span> &T{</span><br><span class="line"> &<span class="keyword">self</span>.x</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>注意,这里我们使用两次,也就是说,我们需要在<code>impl</code>后声明一次泛型名称,再在后续指定泛型。</p><p>这是因为,在泛型结构体里我们可以单独的为某个类型实现方法,而不是一定要所有类型都使用同一个方法,例如:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Point</span><T> {</span><br><span class="line"> x: T,</span><br><span class="line"> y: T</span><br><span class="line">}</span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Point</span><<span class="type">f32</span>> {</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">x</span>(&<span class="keyword">self</span>, other: &Point<<span class="type">f32</span>>) <span class="punctuation">-></span> &<span class="type">f32</span>{</span><br><span class="line"> &<span class="keyword">self</span>.x</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在这段代码里,我们就相当于单独的为<code>f32</code>类型设定了方法x,只有当<code>T</code>的类型为<code>f32</code>时可以使用这个方法。这种写法可以很经常的被用于处理不同类型的不同情况。</p><p>因此,我们需要先声明以下泛型名字,才能确保编译器知道你后面的尖括号到底是泛型还是具体类型。</p><p>当然,我们的方法也可以和函数一样,再次声明自己的泛型名称</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">impl</span> <T, U>Point<T, U> {</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">mixup</span><V, W>(<span class="keyword">self</span>, other:Point<V, W>)<span class="punctuation">-></span>Point<T, W>{</span><br><span class="line"> Point{</span><br><span class="line"> x: <span class="keyword">self</span>.x,</span><br><span class="line"> y: other.y,</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码里,我们在<code>mixup</code>函数里新定义了<code>V, W</code>两个泛型,用来接收可能不同类型的其他<code>Point</code>实例,并把两个实例的类型进行混合后,作为新的<code>Point</code>返回</p><h3 id="在枚举类型定义中使用"><a href="#在枚举类型定义中使用" class="headerlink" title="在枚举类型定义中使用"></a>在枚举类型定义中使用</h3><p>在之前章节的学习里,我们就知道了<code>Result</code>和<code>Option</code>枚举。其中<code>Option</code>就是典型的单泛型枚举,<code>Result</code>就是典型的包含两个泛型的枚举</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Option</span><T>{</span><br><span class="line"> <span class="title function_ invoke__">Some</span>(T),</span><br><span class="line"> <span class="literal">None</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">enum</span> <span class="title class_">Result</span><T, E>{</span><br><span class="line"> <span class="title function_ invoke__">Ok</span>(T),</span><br><span class="line"> <span class="title function_ invoke__">Err</span>(E),</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="泛型的性能"><a href="#泛型的性能" class="headerlink" title="泛型的性能"></a>泛型的性能</h2><p>可能有人会担心,泛型会不会和Python一样,使得程序有运行时的性能影响?</p><p>实际上是不会的,Rust的泛型和c++的auto差不多,会在编译器就静态固定好对应的类型,因此不会产生运行时的损耗,只会在编译期有性能损耗</p><h1 id="trait:定义共享行为"><a href="#trait:定义共享行为" class="headerlink" title="trait:定义共享行为"></a>trait:定义共享行为</h1><p><code>trait</code>(特征?)是用来描述一个类型的功能,可以用来和多个类型共享。例如说求和,每个类型的求和都不尽相同的时候,你可以定义一个trait名为sum,然后再分别为不同的类型实现sum</p><p><code>trait</code>可能和其他语言的<code>interface</code>功能类似,但也不完全相同</p><h2 id="定义trait"><a href="#定义trait" class="headerlink" title="定义trait"></a>定义trait</h2><p>现在假设我们有两个结构体类型,一个是文章(Article),一个是推特(Tweet),我们需要同时为这两个文字内容主体生成摘要,于是我们就可以定义一个Summary的trait,来规定一个适用于所有类型的生成摘要的接口。</p><p>接下来我们新建一个库文件<code>lib.rs</code>,然后定义一个trait</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pub trait Summary {</span><br><span class="line"> fn summarize(&self) -> String;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在这段代码里,首先我们定义了一个公有的trait <code>Summary</code>,并规定了trait里有一个函数<code>summarize</code>,在 trait 里,称作<strong>签名</strong>,它传入类型实例自己,返回<code>String</code>,但这里我们省略了函数的具体实现,具体实现交由不同的类型按照自己的规则来进行实现。</p><p>当然一个trait里可以有多个签名,这里只定义了一个。</p><h2 id="实现trait"><a href="#实现trait" class="headerlink" title="实现trait"></a>实现trait</h2><p>接下来,我们就需要给文章和推特两个结构体类型实现一下用于提取摘要的trait</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">pub struct Article{</span><br><span class="line"> pub title: String,</span><br><span class="line"> pub location: String,</span><br><span class="line"> pub author: String,</span><br><span class="line"> pub content: String,</span><br><span class="line">}</span><br><span class="line">impl Summary for Article {</span><br><span class="line"> fn summarize(&self) -> String {</span><br><span class="line"> format!("{}, by {} ({})", self.title, self.author, self.location)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">pub struct Tweet{</span><br><span class="line"> pub username: String,</span><br><span class="line"> pub content: String,</span><br><span class="line">}</span><br><span class="line">impl Summary for Tweet {</span><br><span class="line"> fn summarize(&self) -> String {</span><br><span class="line"> format!("{}: {}", self.username, self.content)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码,我们定义了两个公有的结构体<code>Article</code>和<code>Tweet</code>,并使用<code>impl Summary for xxx</code>语句,声明为结构体实现<code>Summary</code>这个trait,然后再在<code>impl</code>块里,实现trait内的签名<code>summarize</code>,这时候就可以根据实际情况来返回不同的摘要了。代码中我们是使用<code>format!</code>格式化返回不同的内容。</p><p>实现后,我们就可以在具体的实例里使用这个trait了</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">use test10::{Tweet, Summary};</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let test = Tweet{</span><br><span class="line"> username: String::from("TwoSix"),</span><br><span class="line"> content: String::from("Hello, world!"),</span><br><span class="line"> };</span><br><span class="line"> println!("A new tweet: {}", test.summarize());</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>注意看我们的use代码<code>use test10::{Tweet, Summary}</code>,这里我们同时引进<code>Tweet</code>类型和<code>Summary</code>这个trait,才能让<code>Tweet</code>实例使用trait对应的成员函数,否则会编译报错,不信你可以试试。(test10是我自己创建的根目录名字)</p><p>这一点和其他语言都不一样,有点让人迷惑,实现了trait之后难道不是相当于结构体的成员函数了吗?为什么成员还需要额外引进才能使用?</p><p>这是因为trait提供了相当的灵活性,以至于编译器并不好自动检查怎么使用,例如以下场景:我们实现了两个trait,Summary1 和 Summary2,并且这两个trait里都有一个签名叫做 summarize ,然后我们还在Tweet 结构体里同时实现了这两个 trait</p><p>是的,Rust允许这种场景的存在,那你说这时候调用 summarize时,应该调用的是Summary1 还是 Summary2?因此,必须显示引入,才能正常使用。</p><p>使用就如此,那实现自然也是,如果你想实现别人定义的 trait,那你就需要把别人的 trait 显示引入当前的作用域,才能实现别人的 trait。</p><h2 id="默认实现"><a href="#默认实现" class="headerlink" title="默认实现"></a>默认实现</h2><p>前面我们没有在trait内实现<code>summarize</code>签名,交由每个类型自己实现,但实际上我们也可以为其定义一个默认实现。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pub trait Summary {</span><br><span class="line"> fn summarize(&self) -> String{</span><br><span class="line"> String::from("(Read more...)")</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们在trait的定义内实现了<code>summarize</code>签名,默认返回一个 (Read more…) 的字符串</p><p>然后我们修改一下<code>Article</code>的实现</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">pub struct Article{</span><br><span class="line"> pub title: String,</span><br><span class="line"> pub location: String,</span><br><span class="line"> pub author: String,</span><br><span class="line"> pub content: String,</span><br><span class="line">}</span><br><span class="line">impl Summary for Article {}</span><br></pre></td></tr></table></figure></div><p>我们在实现<code>Summary</code>的时候,直接使用空的花括号,没有实现具体的 trait 签名,然后我们再使用看一下效果。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">use test10::{Tweet, Article, Summary};</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let test_tweet: Tweet = Tweet{</span><br><span class="line"> username: String::from("TwoSix"),</span><br><span class="line"> content: String::from("Hello, world!"),</span><br><span class="line"> };</span><br><span class="line"> let test_article = Article{</span><br><span class="line"> title: String::from("Hello"),</span><br><span class="line"> location: String::from("World"),</span><br><span class="line"> author: String::from("TwoSix"),</span><br><span class="line"> content: String::from("This is a test"),</span><br><span class="line"> };</span><br><span class="line"> println!("A new article: {}", test_tweet.summarize());</span><br><span class="line"> println!("A new tweet: {}", test_article.summarize());</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>没有意外,正常的输出 (Read more…),并且 Tweet 的输出正常,不会受到影响。这个概念也很常见,也就是重载。</p><h2 id="把trait作为参数"><a href="#把trait作为参数" class="headerlink" title="把trait作为参数"></a>把trait作为参数</h2><p>trait 甚至能作为函数的参数传入,见以下示例</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">use test10::{Tweet, Article, Summary};</span><br><span class="line"></span><br><span class="line">fn summarize(item: impl Summary) -> String{</span><br><span class="line"> item.summarize()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let test_tweet: Tweet = Tweet{</span><br><span class="line"> username: String::from("TwoSix"),</span><br><span class="line"> content: String::from("Hello, world!"),</span><br><span class="line"> };</span><br><span class="line"> let test_article = Article{</span><br><span class="line"> title: String::from("Hello"),</span><br><span class="line"> location: String::from("World"),</span><br><span class="line"> author: String::from("TwoSix"),</span><br><span class="line"> content: String::from("This is a test"),</span><br><span class="line"> };</span><br><span class="line"> println!("A new article: {}", summarize(test_tweet));</span><br><span class="line"> println!("A new tweet: {}", summarize(test_article));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>我们定义了一个函数<code>summarize</code>来调用每个实现了 Summary trait 的类型的 summarize 函数。这里的<code>impl Summary</code> 就是指代的所有实现了 Summary trait 的类型。</p><h3 id="trait约束"><a href="#trait约束" class="headerlink" title="trait约束"></a>trait约束</h3><p>以上<code>impl Summary</code>实际上只是一个语法糖,它的完整声明形式称作 triat 约束,写作如下</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">fn summarize<T: Summary>(item: T) -> String{</span><br><span class="line"> item.summarize()</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>意思就是声明了一个泛型<code>T</code>,并使用<code>:Summary</code>对泛型T指代的类型进行了约束,使它只能代表实现了 Summary trait 的类型。</p><p>实际上在函数较复杂的时候,triat 约束要比之前的语法糖要好用</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fn summarize<T: Summary>(item1: T, item2:T, item3: T) -> String{</span><br><span class="line"> item1.summarize();</span><br><span class="line"> item2.summarize();</span><br><span class="line"> item3.summarize()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn summarize(item1: impl Summary, item2:impl Summary, item3: impl Summary) -> String{</span><br><span class="line"> item1.summarize();</span><br><span class="line"> item2.summarize();</span><br><span class="line"> item3.summarize()</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>对比一下这两种写法,是不是在复杂的情况,反而 triat 约束更简洁了一些?</p><h3 id="多个trait约束"><a href="#多个trait约束" class="headerlink" title="多个trait约束"></a>多个trait约束</h3><p>如果我想让泛型 T 指代实现了多个 triat 的类型怎么办?使用 + 法可以解决这个问题</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">fn summarize<T: Summary + Display>(item: T) -> String{</span><br><span class="line"> item.summarize()</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里就表示,传入的 item 必须是同时实现了 Summary trait 和标准库的 Display trait 的类型。</p><h3 id="简化trait约束"><a href="#简化trait约束" class="headerlink" title="简化trait约束"></a>简化trait约束</h3><p>当有多个泛型参数,每个泛型参数有多个 trait 约束的时候,会写成这样</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fn summarize<T: Summary + Display, U: Summary + Display>(item1: T, item2: U) -> String{</span><br></pre></td></tr></table></figure></div><p>这样就会导致函数定义很长又有很多重复内容,阅读费劲,难以理解,所以 rust 提供了一个 where 从句的方法,提高这种情况下的可读性</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fn summarize<T, U>(item1: T, item2: U) -> String</span><br><span class="line"> where T: Summary + Display,</span><br><span class="line"> U: Summary + Display</span><br><span class="line">{</span><br></pre></td></tr></table></figure></div><p>我们可以在返回值的类型后面,加上一个 where 从句,把每个泛型的 trait 约束换一行之后再定义,就美观多了。</p><h2 id="把trait作为函数返回值类型"><a href="#把trait作为函数返回值类型" class="headerlink" title="把trait作为函数返回值类型"></a>把trait作为函数返回值类型</h2><p>既然能作为函数参数传入,自然也能作为函数返回值进行返回了</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fn summarize() -> impl Summary</span><br><span class="line">{</span><br><span class="line"> Tweet{</span><br><span class="line"> username: String::from("TwoSix"),</span><br><span class="line"> content: String::from("Hello, world!"),</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这一段代码则让 summarize 函数固定返回实现了 Summary 的 Tweet 类型,但这种用法似乎感觉没有什么用?没关系,书上说后续讲解闭包等概念的时候,会使用到这种语法。</p><p>需要注意的是,Rust 编译器同样会对 impl Trait 进行静态推理保存,碍于 impl Trait 工作方式的限制,所以你只能在返回一个类型的时候,使用 trait 作为返回值类型,如果你既想返回 Tweet 也想返回 Article 是不行的。</p><p>如以下的代码就会报错</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">fn summarize(switch: bool) -> impl Summary</span><br><span class="line">{</span><br><span class="line"> if switch {</span><br><span class="line"> Tweet{</span><br><span class="line"> username: String::from("TwoSix"),</span><br><span class="line"> content: String::from("Hello, world!"),</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> Article{</span><br><span class="line"> title: String::from("Hello"),</span><br><span class="line"> location: String::from("World"),</span><br><span class="line"> author: String::from("TwoSix"),</span><br><span class="line"> content: String::from("This is a test"),</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="练手"><a href="#练手" class="headerlink" title="练手"></a>练手</h2><p>还记得我们之前在讲泛型的时候使用的查找最大值的例子吗,之前代码编译不通过,但现在的我们已经有办法修复它了。</p><p>先回顾以下这段代码</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fn largest<T>(list: &[T]) -> T{</span><br><span class="line"> let mut largest = list[0];</span><br><span class="line"> for &item in list.iter() {</span><br><span class="line"> if item > largest {</span><br><span class="line"> largest = item;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> largest</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>报错的是 <code>></code> 号不能用于泛型<code>T</code>,而 <code>></code> 实际上是一个叫做 <code>PartialOrd</code> 的 trait,所以这段代码报错的核心是,不是每个类型都实现了 <code>PartialOrd</code>,所以我们可以给它加个 trait 约束。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fn largest<T: PartialOrd>(list: &[T]) -> T{</span><br><span class="line"> let mut largest = list[0];</span><br><span class="line"> for &item in list.iter() {</span><br><span class="line"> if item > largest {</span><br><span class="line"> largest = item;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> largest</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>因为 <code>PartialOrd</code> 是预导入模块,所以我们可以直接使用,而不需要 use。我们修改完后,这段代码出现了新的报错:<code>cannot move out of here | move occurs because list[_] has type T, which does not implement the Copy trait</code>;意思就是,不是每个类型都实现了 Copy trait,所以我们没有办法把泛型 T 列表内的元素赋值出来,解决也很简单,那就是再加个 Copy 约束即可。</p><p>以下这段代码,就能正确的编译并找到不同类型数组的最大值了:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">fn largest<T: PartialOrd+Copy>(list: &[T]) -> T{</span><br><span class="line"> let mut largest = list[0];</span><br><span class="line"> for &item in list.iter() {</span><br><span class="line"> if item > largest {</span><br><span class="line"> largest = item;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> largest</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main(){</span><br><span class="line"> let a = vec![1, 2, 3, 4, 5];</span><br><span class="line"> let b = vec!['a', 'b', 'c', 'd', 'e'];</span><br><span class="line"> let largest_a = largest(&a);</span><br><span class="line"> let largest_b = largest(&b);</span><br><span class="line"> println!("The largest number is {}", largest_a);</span><br><span class="line"> println!("The largest char is {}", largest_b);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><blockquote><p>PS:</p><p>这里我有点迷惑,这难道不是对应赋值吗?什么类型不能赋值出来?我查了一下资料,大致得出的结论如下,不知道对不对:</p><p>翻译应该沾点锅,看了下原文的意思应该是:不是所有类型都有 Copy trait,这里的 Copy trait 指的是深拷贝,在浅拷贝的变量里,赋值操作应该是 move,而 move 则对应了所有权的转移,对于一个列表内的变量,我们把它所有权转移出来之后,但数组自己是不知道自己的元素所有权已经没有了,这不就出问题了?</p><p>所以最重要的原因还是在于这一句代码:<code>let mut largest = list[0];</code> 这里把<code>list[0]</code>的元素所有权移出来了,自然有问题。所以我把代码改成下面这个样子,多加了一些引用的使用,不转移所有权,也就不需要使用Copy约束了。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fn largest<T: PartialOrd>(list: &[T]) -> &T{</span><br><span class="line"> let mut largest = &list[0];</span><br><span class="line"> for item in list.iter() {</span><br><span class="line"> if item > largest {</span><br><span class="line"> largest = item;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> largest</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div></blockquote><h2 id="通过约束为指定类型实现方法"><a href="#通过约束为指定类型实现方法" class="headerlink" title="通过约束为指定类型实现方法"></a>通过约束为指定类型实现方法</h2><p>也就是当我们定义了一个泛型结构体时,可以让这个结构体内的一些方法只能让指定的类型调用。</p><p>例如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">use std::fmt::Display;</span><br><span class="line"></span><br><span class="line">struct Pair<T> {</span><br><span class="line"> x: T,</span><br><span class="line"> y: T,</span><br><span class="line">}</span><br><span class="line">impl<T> Pair<T> {</span><br><span class="line"> fn new(x: T, y: T) -> Self {</span><br><span class="line"> Self {</span><br><span class="line"> x,</span><br><span class="line"> y,</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">impl<T: PartialOrd+Display> Pair<T> {</span><br><span class="line"> fn cmp_display(&self){</span><br><span class="line"> if self.x >= self.y{</span><br><span class="line"> println!("The largest member is x = {}", self.x);</span><br><span class="line"> } else {</span><br><span class="line"> println!("The largest member is y = {}", self.y);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main(){</span><br><span class="line"> let pair = Pair::new(1, 2);</span><br><span class="line"> pair.cmp_display();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们就规定了 <code>Pair</code> 结构体里只有存放的类型是同时实现了可以比较可以打印两个 trait 的类型,才能调用<code>cmp_display</code>这个方法。(注意 <code>Display</code> trait 不是预导入的,虽然是标准库,也要自己 use)</p><p>当然,基于此,自然也可以为结构体的指定类型实现 trait。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">impl<T:Display> ToString for Pair<T> {</span><br><span class="line"> fn to_string(&self) -> String {</span><br><span class="line"> format!("x = {}, y = {}", self.x, self.y)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main(){</span><br><span class="line"> let pair = Pair::new(1, 2);</span><br><span class="line"> println!("{}", pair.to_string());</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h1 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h1><p>普通的泛型可以用来消除重复代码,也可以向编译器指明程序员希望这些类型拥有什么样的行为,而生命周期就是一种特殊的泛型,用来<strong>确保引用在我们使用的过程中一直有效</strong></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在介绍生命周期前,我们需要介绍编译器是怎么检查因为超出作用域而导致的悬垂引用问题的。我们来看看以下这个例子</p><p><img lazyload src="/images/loading.svg" data-src="/images/image.png" alt="img" ></p><p>这个例子中,我们定义了一个变量<code>r</code>,在下一个花括号中,我们定义了一个变量<code>x</code>,并把<code>x</code>的所有权借给<code>r</code>,但x的作用域只在这个花括号为止,超出了这个花括号之后所有权就被回收了,<code>r</code>也就成了悬垂引用。</p><p>右边的<code>'a,'b</code>就分别代表了r和x的生命周期,我们可以明显的看到,x的生命周期’b明显要比r的生命周期’a短,所以编译器就可以通过检查生命周期的长短来查找你可能的悬垂引用问题,进而提出报错。</p><p>那我们为什么要指定生命周期?让我们来写一段代码</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fn longest(x: &str, y: &str) -> &str {</span><br><span class="line"> if x.len() > y.len() {</span><br><span class="line"> x</span><br><span class="line"> } else {</span><br><span class="line"> y</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这个函数用于比较得到长度更长的字符串,因为不想只是比较以下就夺取所有权,所以使用引用的方式传入,也使用引用的方式返回。</p><p>不出意外,会有错误提示<code>missing lifetime specifier</code>,我们回顾以下编译器检查悬垂引用的方式,需要比较一下引用的生命周期长短,而在以上情况中,我们有一个分支判断,既可能返回x,也可能返回y,编译器不知道会返回x还是返回y,也不知道该比较哪个和哪个引用之间的长短,也就无法进行检查,进而报错,提示我们需要指定生命周期,明确一下引用之间的关系,方便编译器进行比较。</p><h2 id="标注生命周期"><a href="#标注生命周期" class="headerlink" title="标注生命周期"></a>标注生命周期</h2><h3 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h3><p>标注生命周期的语法很简单,和我们之前举例的命名一样,生命周期的命名以<code>'</code>开头,如<code>'a</code></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&i32 // 这是一个普通引用</span><br><span class="line">&'a i32 // 这是一个生命周期为'a的引用</span><br><span class="line">&'a mut i32 // 这是一个生命周期为'a的可变引用</span><br></pre></td></tr></table></figure></div><h3 id="函数中的生命周期标注"><a href="#函数中的生命周期标注" class="headerlink" title="函数中的生命周期标注"></a>函数中的生命周期标注</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {</span><br><span class="line"> if x.len() > y.len() {</span><br><span class="line"> x</span><br><span class="line"> } else {</span><br><span class="line"> y</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>可见,我们用类似定义泛型的方式,定义了一个生命周期,名为<code>'a</code>,并且给后面的引用都指定了生命周期为<code>'a</code>,也就是告诉编译器,这个函数里传入的引用必然都是相同的生命周期,放心比较!于是编译器就会选择一个引用,推导出实际的生命周期<code>'a</code>,然后和函数外的实际拥有所有权的变量进行比较,然后发现外边的变量生命周期都比<code>'a</code>长,最后得出结果,这段代码可以编译通过。</p><p>那x和y是两个不同的变量啊,这里编译器最终得到的生命周期’a究竟是什么地方的生命周期?</p><p>答案也很简单,就是x和y重叠部分的生命周期</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>(){</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">"abcd"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span> = <span class="string">"xyz"</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">result</span> = <span class="title function_ invoke__">longest</span>(a.<span class="title function_ invoke__">as_str</span>(), b);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The longest string is {}"</span>, result);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>如以上代码,<code>'a</code>的长度等同于变量<code>b</code>的生命周期(a,b重叠部分,也就是取最短的一个就行),我们定义了返回的引用生命周期也是<code>'a</code>,因此返回的<code>result</code>生命周期也应该是在<code>b</code>的生命周期范围内,这段代码里和<code>b</code>一起被回收,所以没有问题,编译通过。</p><p>错误示例如下,result变量的生命周期要长于b的生命周期,则无法通过编译:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>(){</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">result</span>;</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">"abcd"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span> = <span class="string">"xyz"</span>;</span><br><span class="line"> result = <span class="title function_ invoke__">longest</span>(a.<span class="title function_ invoke__">as_str</span>(), b);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The longest string is {}"</span>, result);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="深入理解生命周期"><a href="#深入理解生命周期" class="headerlink" title="深入理解生命周期"></a>深入理解生命周期</h2><p>由上面我们可以知道,其实标注生命周期的作用就是为了方便编译器检查。</p><p>所以自然而然的,不需要参与检查的变量也就不是必须标注的了,如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">fn longest<'a>(x: &'a str, y: &str) -> &'a str {</span><br><span class="line"> x</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们规定直接返回x,所以编译器只会顺着第一个参数x进行检查,所以我们只标注了x的生命周期,不标注y也是可以编译通过的,因为y和返回值没有半毛钱关系。</p><p>其次,我们标注生命周期,只是向编译器声明了以下传入的引用的生命周期关系,<strong>并没有改变任意一方的生命周期</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fn longest<'a>(x: &str, y: &str) -> &'a str {</span><br><span class="line"> let result = String::from("really long string");</span><br><span class="line"> result.as_str()</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>例如这一段代码,我们给返回值声明了生命周期<code>'a</code>,但返回值<code>result</code>是在函数内定义的,他也就只能活在这个函数里,并不是说我们给他声明了一个生命周期,他就能活到外面去了。</p><h2 id="结构体中的生命周期"><a href="#结构体中的生命周期" class="headerlink" title="结构体中的生命周期"></a>结构体中的生命周期</h2><p>一般情况下结构体都是存储自持有的变量,但实际上也可以存储引用,这时候就需要用到生命周期</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">struct ImportantExcerpt<'a> {</span><br><span class="line"> part: &'a str,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main(){</span><br><span class="line"> let novel = String::from("Call me Ishmael. Some years ago...");</span><br><span class="line"> let first_sentence = novel.split('.').next().expect("Could not find a '.'");</span><br><span class="line"> let i = ImportantExcerpt { part: first_sentence };</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>定义的语法也是类似即可。</p><h2 id="生命周期省略"><a href="#生命周期省略" class="headerlink" title="生命周期省略"></a>生命周期省略</h2><p>可以说,所有引用必然是需要有自己的生命周期的,但其实以前编写的很多函数都没有指明生命周期,也能传入引用,是为什么呢?</p><p>其实早期的Rust是所有引用都必须显示标注生命周期的,但随着慢慢的发展,Rust的团队发现有很多情况下,是能够使用编译器推导出返回值的生命周期的,重复的写生命周期有点烦,也就把这部分情况,写成了可省略的生命周期规则。</p><p>编译器检查生命周期的规则有以下三条:</p><ol><li>每一个引用的参数,都有自己的生命周期</li><li>当只存在一个输入的生命周期参数时,这个生命周期会被赋予给所有输出的生命周期参数</li><li>当拥有多个输入的生命周期参数时,若其中一个是<code>&self</code>或<code>&mut self</code>,<code>self</code>的生命周期会被赋予给所有输出的生命周期参数</li><li>若以上三条规则使用完毕,编译器仍然无法推导出所有生命周期,则报错,让用户指定。</li></ol><p>这些规则帮助我们省略了很多生命周期的编写。为了更好了理解这些规则,我们举一些例子看看。</p><p>例如这段代码:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fn test(s: &str)->&str{</span><br></pre></td></tr></table></figure></div><p>按照规则1,编译器先给所有输入参数赋予自己的生命周期</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fn test<'a>(s: &'a str)->&str{</span><br></pre></td></tr></table></figure></div><p>由于只有一个输入参数s,满足规则2,编译器把生命周期赋予给所有输出的参数</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fn test<'a>(s: &'a str)->&'a str{</span><br></pre></td></tr></table></figure></div><p>至此,编译器自己推导出了所有参数的生命周期,也就不用我们写了。</p><p>接着,我们再距离说明一下规则3</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">struct ImportantExcerpt<'a> {</span><br><span class="line"> part: &'a str,</span><br><span class="line">}</span><br><span class="line">impl <'a> ImportantExcerpt<'a> {</span><br><span class="line"> fn announce_and_return_part(&self, announcement: &str) -> &str {</span><br><span class="line"> self.part</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>impl <'a> ImportantExcerpt<'a></code>这个声明语句中的生命周期声明不能省略(我也不知道为什么)</p><p>在方法<code>announce_and_return_part</code>中,编译器会首先按照规则1,赋予声明周期</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fn announce_and_return_part(&'a self, announcement: &'b str) -> &str {</span><br></pre></td></tr></table></figure></div><p>因为有两个参数,规则不生效</p><p>最后因为参数里有self,所以按照规则3,赋予输出参数self的声明周期</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fn announce_and_return_part(&'a self, announcement: &'b str) -> &'a str {</span><br></pre></td></tr></table></figure></div><p>这时候,所有生命周期也就推导完毕了,不需要手动指定,也可编译通过。</p><blockquote><p>如果不返回<code>self.part</code>,返回<code>announcement</code>一样会报错,但此时报错的提示是announcement的生命周期不一定比self长,而不是缺少生命周期声明,可见编译器确实给输出赋予了self的声明周期,并进行检查</p></blockquote><h2 id="静态生命周期"><a href="#静态生命周期" class="headerlink" title="静态生命周期"></a>静态生命周期</h2><p>一种特殊的生命周期,意味在整个程序的执行期中都可以存活</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">let s:&'static str = "I have a static lifetime.";</span><br></pre></td></tr></table></figure></div><p>但使用需要谨慎,1:你需要确保他确实可以在整个程序的生命周期存活;2:你确定它真的需要活这么长时间。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>最后用一段代码,同时使用泛型、trait约束、生命周期</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">use std::fmt::Display;</span><br><span class="line"></span><br><span class="line">fn longest_with_ann<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str</span><br><span class="line"> where T: Display</span><br><span class="line">{</span><br><span class="line"> println!("Announcement! {}", ann);</span><br><span class="line"> if x.len() > y.len(){</span><br><span class="line"> x</span><br><span class="line"> }</span><br><span class="line"> else{</span><br><span class="line"> y</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">fn main(){</span><br><span class="line"> let string1 = String::from("abcd");</span><br><span class="line"> let string2 = "xyz";</span><br><span class="line"> let result = longest_with_ann(string1.as_str(), string2, 123);</span><br><span class="line"> println!("The longest string is {}", result);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>简单解释一下代码</p><ol><li>定义了一个声明周期<code>'a</code>,用来声明传入的两个字符串x, y的生命周期,以及返回字符串的生命周期</li><li>定义了一个泛型<code>T</code></li><li>约束了泛型<code>T</code>只能是实现了<code>Display</code>这个trait的类型,方便后续直接使用<code>println!</code>输出</li></ol>]]></content>
<summary type="html"><h1 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h1><p>泛型是一种具体类型或者其他属性的抽象替代,通常用来减少代码的重复,接下来将从泛型的几个实际应用场景开始介绍泛型</p>
<h2 id="应用</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>使用Sphinx为你的项目快速构建文档</title>
<link href="https://twosix.page/2023/04/15/%E4%BD%BF%E7%94%A8Sphinx%E4%B8%BA%E4%BD%A0%E7%9A%84%E9%A1%B9%E7%9B%AE%E5%BF%AB%E9%80%9F%E6%9E%84%E5%BB%BA%E6%96%87%E6%A1%A3/"/>
<id>https://twosix.page/2023/04/15/%E4%BD%BF%E7%94%A8Sphinx%E4%B8%BA%E4%BD%A0%E7%9A%84%E9%A1%B9%E7%9B%AE%E5%BF%AB%E9%80%9F%E6%9E%84%E5%BB%BA%E6%96%87%E6%A1%A3/</id>
<published>2023-04-14T17:13:00.000Z</published>
<updated>2024-07-04T15:52:28.396Z</updated>
<content type="html"><![CDATA[<p>最近写了个软件,需要写个接口文档,看到别人项目的文档有不少都是托管在 Read the Docs 上的,于是搜了一下,Read the Docs 是一个托管平台,而这个平台的文档是基于 Sphinx 构建的,所以就学了一下,以此记录。</p><h1 id="安装Sphinx"><a href="#安装Sphinx" class="headerlink" title="安装Sphinx"></a>安装Sphinx</h1><p>很简单,用pip安装即可,尽量使用官方的源,国内源听说多少有点问题</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install sphinx</span><br></pre></td></tr></table></figure></div><h1 id="构建Sphinx项目"><a href="#构建Sphinx项目" class="headerlink" title="构建Sphinx项目"></a>构建Sphinx项目</h1><h2 id="快速构建"><a href="#快速构建" class="headerlink" title="快速构建"></a>快速构建</h2><p>推荐在项目的根目录构建一个文件夹<code>docs</code>来专门存放文档的源码,然后<code>cd docs</code>下构建源码</p><p>构建也非常简单,一行命令即可</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sphinx-quickstart</span><br></pre></td></tr></table></figure></div><p>输入命令后,会提示是否要创建独立目录,选择<code>y</code>是即可,然后提示你填一些信息,包括项目名,作者名,语言等等,照实填写即可,语言是简体中文的话,填 <code>zh_CN</code> 即可</p><p>构建完毕后,如果之前选择的是<code>y</code>的话,我们就可以在目录下看到<code>build</code>和<code>source</code>文件夹了,其中<code>source</code>文件夹就是存放项目源码的文件夹</p><p><code>source</code>中包含了一个<code>conf.py</code>文件,用于填写项目配置,一个<code>index.rst</code>文件,是首页的源码。</p><p>若无特殊需求的话,直接根据<code>rst</code>格式编写你的代码,然后<code>make html</code>即可完成项目的构建。</p><p>但是为了写一个文档,又专门去学一个 rst 语法似乎有些不大合适,所以需要修改一下配置文件,让Sphinx支持大众都熟知的 markdown 语法。</p><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><h2 id="markdown配置"><a href="#markdown配置" class="headerlink" title="markdown配置"></a>markdown配置</h2><h3 id="安装扩展"><a href="#安装扩展" class="headerlink" title="安装扩展"></a>安装扩展</h3><p>首先,为了支持 markdown 语法,我们需要安装一个扩展插件<code>myst-parser</code></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install --upgrade myst-parser</span><br></pre></td></tr></table></figure></div><h3 id="添加扩展配置"><a href="#添加扩展配置" class="headerlink" title="添加扩展配置"></a>添加扩展配置</h3><p>安装完成后,我们在<code>conf.py</code>文件内,修改一下<code>extensions</code>字段,引入扩展即可</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extensions = ['myst_parser']</span><br></pre></td></tr></table></figure></div><p>如果你的 markdown 文件可能非 md 结尾,则需要添加一下<code>source_suffix</code>字段</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">source_suffix = {</span><br><span class="line"> '.rst': 'restructuredtext',</span><br><span class="line"> '.txt': 'markdown',</span><br><span class="line"> '.md': 'markdown',</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>意思就是,<code>.rst</code>文件,使用<code>restructuredtext</code>进行解析,<code>.txt</code>和<code>.md</code>文件则使用 markdown 进行解析(.rst不能删,首页还得用)</p><p>此外,<code>myst-parser</code>默认关闭了很多一些非基本markdown的语法,我们可以通过添加<code>myst_enable_extensions</code>字段来支持这些语法,以下是一个完整的示例:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">myst_enable_extensions = [</span><br><span class="line"> "amsmath",</span><br><span class="line"> "attrs_inline",</span><br><span class="line"> "colon_fence",</span><br><span class="line"> "deflist",</span><br><span class="line"> "dollarmath",</span><br><span class="line"> "fieldlist",</span><br><span class="line"> "html_admonition",</span><br><span class="line"> "html_image",</span><br><span class="line"> "linkify",</span><br><span class="line"> "replacements",</span><br><span class="line"> "smartquotes",</span><br><span class="line"> "strikethrough",</span><br><span class="line"> "substitution",</span><br><span class="line"> "tasklist",</span><br><span class="line">]</span><br></pre></td></tr></table></figure></div><p>按需开启即可,每个语法扩展具体功能如下:</p><ul><li>amsmath:LaTeX数学公式的软件包</li><li>attrs_inline:属性扩展,这个我不太了解,应该和HTML的写法相关</li><li>colon_fence:表格的语法</li><li>deflist:列表的语法,也就是我现在在写的这个无序列表</li><li>dollarmath:使用美元符号$$包围的数学公式语法</li><li>fieldlist:块列表语法,一般用在说明函数及其参数的功能的时候</li><li>html_admonition:基于html的提示框语法</li><li>html_image:基于html的图片显示语法</li><li>linkify:网址链接可点击的语法</li><li>replacements:这个我不太懂,看起来是可以支持字符串替换</li><li>smartquotes:会帮你自动把直引号转换成弯引号</li><li>strikethrough:删除线语法,就是这样</li><li>substitution:替换语法,差不多就是你可以定义一个变量,然后在后续的文本里添加占位符,构建时会帮你自动把变量的值填进占位符里</li><li>tasklist:todo列表语法</li></ul><p>因篇幅有限,扩展仅作简单概述,甚至可能不准确,具体每个扩展的使用方法,以及效果示例,请查看<a class="link" href="https://myst-parser.readthedocs.io/en/latest/syntax/optional.html" >官方文档<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><blockquote><p>linkify 需要额外安装一个插件,<code>pip install linkify-it-py</code></p></blockquote><h2 id="主题配置"><a href="#主题配置" class="headerlink" title="主题配置"></a>主题配置</h2><p>默认的主题很丑,所以我们选择使用 read the docs 的主题配置</p><p>首先安装一下主题</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install sphinx_rtd_theme</span><br></pre></td></tr></table></figure></div><p>然后在<code>conf.py</code>文件修改一下<code>html_theme</code>字段即可,改为</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">html_theme = 'sphinx_rtd_theme'</span><br></pre></td></tr></table></figure></div><h2 id="编写你的文档"><a href="#编写你的文档" class="headerlink" title="编写你的文档"></a>编写你的文档</h2><p>因为我们配置了 markdown 语法,所以我们只需要使用常规的markdown编译器,正常的写每一页文档即可。</p><p>这里我就写了两页,代码结构+如何添加算法,如下,写完放进source的目录即可</p><p><img lazyload src="/images/loading.svg" data-src="/images/blog1.jpg" alt="img" ></p><blockquote><p>注意,markdown语法需要把最高级的标题留给页面标题,例如用#一级标题写了页面的标题后,文章内容就只能用二级及以下的标题了,不然后面目录显示会有问题,会把文件所有最高级标题都作为目录标题</p></blockquote><h2 id="修改主页代码"><a href="#修改主页代码" class="headerlink" title="修改主页代码"></a>修改主页代码</h2><p>接下来我们去 <code>index.rst</code> 文件下,修改一下我们的主页内容以及左侧目录内容就差不多了</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">.. AlgorithmViewer documentation master file, created by</span><br><span class="line"> sphinx-quickstart on Fri Apr 14 13:50:38 2023.</span><br><span class="line"> You can adapt this file completely to your liking, but it should at least</span><br><span class="line"> contain the root `toctree` directive.</span><br><span class="line"></span><br><span class="line">Welcome to AlgorithmViewer's documentation!</span><br><span class="line">===========================================</span><br><span class="line"></span><br><span class="line">.. toctree::</span><br><span class="line"> :maxdepth: 2</span><br><span class="line"> :caption: Contents</span><br><span class="line"> </span><br><span class="line"> 代码结构</span><br><span class="line"> 如何添加算法</span><br></pre></td></tr></table></figure></div><p>rst 的语法我也不太懂,所以只简单针对这一个文件作简单的分析</p><p>.. 开头的类似于注释,不会被编译到网页上</p><p>===========================================上的一行就是我们的欢迎页标题,rst的标题等号长度不得小于文字长度</p><p>toctree:: 声明了一个树状结构,也就是我们的目录,maxdepth就是层级的最深深度,2也就是只显示两层</p><p>caption 指定目录的标题,这里的目录标题是 Contents</p><p>然后在后面接上你编写的文档文件即可,按我的写法,最终生成的页面会是这样的</p><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-04-15_01-09-42-1024x614.jpg" alt="img" ></p><h2 id="生成HTML文件"><a href="#生成HTML文件" class="headerlink" title="生成HTML文件"></a>生成HTML文件</h2><p>配置完成后,我们就可以在根目录<code>docs</code>下执行编译命令了,也是一行代码的事</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make html</span><br></pre></td></tr></table></figure></div><p>成功 build 后,我们就可以到 <code>build/html</code> 文件夹下,看到我们的HTML文件了,打开 index.html,就可以看到你的文档啦</p>]]></content>
<summary type="html"><p>最近写了个软件,需要写个接口文档,看到别人项目的文档有不少都是托管在 Read the Docs 上的,于是搜了一下,Read the Docs 是一个托管平台,而这个平台的文档是基于 Sphinx 构建的,所以就学了一下,以此记录。</p>
<h1 id="安装Sphin</summary>
<category term="好软推荐" scheme="https://twosix.page/categories/%E5%A5%BD%E8%BD%AF%E6%8E%A8%E8%8D%90/"/>
<category term="好软推荐" scheme="https://twosix.page/tags/%E5%A5%BD%E8%BD%AF%E6%8E%A8%E8%8D%90/"/>
</entry>
<entry>
<title>【Rust 学习记录】9. 错误处理</title>
<link href="https://twosix.page/2023/04/06/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%919-%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/"/>
<id>https://twosix.page/2023/04/06/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%919-%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</id>
<published>2023-04-06T14:23:58.000Z</published>
<updated>2024-07-04T15:52:28.395Z</updated>
<content type="html"><![CDATA[ <div class="note-large default"> <div class="notel-title rounded-t-lg p-3 font-bold text-lg flex flex-row gap-2 items-center"> <i class="notel-icon fa-solid fa-info"></i><p>前言</p> </div> <div class="notel-content"> <p>Rust 里的错误主要分为两种:1. 不可恢复错误:主要指的就是程序Bug之类的用户不可见的错误,例如尝试访问超过数组长度的下标;2. 可恢复错误,例如文件没找到等,可以提示用户再次查找。Rust 对这两种错误进行了区分,并针对不同的场景提供了许多的特性来处理</p> </div> </div><h1 id="不可恢复错误与Panic"><a href="#不可恢复错误与Panic" class="headerlink" title="不可恢复错误与Panic!"></a>不可恢复错误与Panic!</h1><h2 id="Panic-宏"><a href="#Panic-宏" class="headerlink" title="Panic!宏"></a>Panic!宏</h2><h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p><code>Panic!</code>宏是专门用于处理某个错误被检测到,而程序员不知道该怎么处理的情景。<code>Panic!</code>宏会首先打印一段错误提示信息,然后沿着调用的栈反向遍历,清理等待执行的指令以及它们的数据,清理完毕后退出程序。</p> <div class="note-large blue"> <div class="notel-title rounded-t-lg p-3 font-bold text-lg flex flex-row gap-2 items-center"> <p>提示</p> </div> <div class="notel-content"> <p>PS: Rust程序打包时会默认附带很多信息来支持<code>Panic!</code>的栈展开清理操作,如果你不需要 Rust 自己清理,可以接受把内存交由操作系统来回收,并且需要打包的二进制包体尽可能小的话,可以在<code>Cargo.toml</code>的<code>[profile]</code>区域添加<code>panic = 'abort'</code>来讲panic的默认行为从展开切换为直接终止。例如</p><div class="code-container" data-rel="Toml"><figure class="iseeu highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[profile.release]</span></span><br><span class="line"><span class="attr">panic</span> = <span class="string">'abort'</span></span><br></pre></td></tr></table></figure></div> </div> </div><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> panic!("crash and burn");</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>运行这段代码,会得到报错</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">thread 'main' panicked at 'crash and burn', src\main.rs:2:5note: run with RUST_BACKTRACE=1 environment variable to display a backtrace</span><br></pre></td></tr></table></figure></div><p>第一句很简单,输出了我们的报错信息,并告诉了我们<code>panic</code>的位置在<code>main.rs</code>第2行第5个字符。</p><p>第二句则提示我们可以用环境变量<code>RUST_BACKTRACE=1</code>显示回溯信息,是什么意思呢?我们可以试一下。设置环境变量后再运行</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// windows系统下,powersell环境的命令</span><br><span class="line">$env:RUST_BACKTRACE=1; cargo run</span><br></pre></td></tr></table></figure></div><p>我们可以看到新的报错信息</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">stack backtrace:</span><br><span class="line"> 0: std::panicking::begin_panic_handler</span><br><span class="line"> at /rustc/8460ca823e8367a30dda430efda790588b8c84d3/library\std\src\panicking.rs:575</span><br><span class="line"> 1: core::panicking::panic_fmt</span><br><span class="line"> at /rustc/8460ca823e8367a30dda430efda790588b8c84d3/library\core\src\panicking.rs:64</span><br><span class="line"> 2: test_error::main</span><br><span class="line"> at .\src\main.rs:2</span><br><span class="line"> 3: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> ></span><br><span class="line"> at /rustc/8460ca823e8367a30dda430efda790588b8c84d3\library\core\src\ops\function.rs:250</span><br></pre></td></tr></table></figure></div><p>可以看到这就是我们<code>panic</code>栈展开的时候的具体信息,首先是<code>panic</code>函数,再是<code>panic</code>标准化输出,然后是我们自己的文件<code>.\src\main.rs</code>的<code>main</code>函数,最后我也不清楚,可能是<code>main</code>函数入口吧。</p><p>我们就可以根据这个回溯信息,一个一个查找到错误发生的地方,进行排除(感觉用的不会很多?)</p><p>另外,后面我们还得运行代码,如果不想看到这么一长串,记得把这个环境变量设回0</p><h1 id="可恢复错误与Result"><a href="#可恢复错误与Result" class="headerlink" title="可恢复错误与Result"></a>可恢复错误与Result</h1><h2 id="Result枚举类型"><a href="#Result枚举类型" class="headerlink" title="Result枚举类型"></a>Result枚举类型</h2><h3 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h3><p>其实我们在之前就已经使用过了<code>Result</code>了,在编写随机数字时,我们在处理用户输入的时候就用了<code>expect</code>函数,那时候我们就简单的提到了,<code>Result</code>是一个枚举类型,包含<code>Ok</code>和<code>Err</code>两个变体。它的定义是这样的:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">enum Result<T,E>{</span><br><span class="line"> Ok(T),</span><br><span class="line"> Err(E),</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>其中<code>T,E</code>是两个泛型参数,下一章就会讨论到泛型。总之就是1. <code>T</code>包含了<code>Ok</code>里的值,跟随着程序执行成功时返回对应的值;2. <code>E</code>包含了错误类型,跟随着执行失败的时候返回</p><p>当一个可能会运行失败的函数,可以将<code>Result</code>作为返回结果,例如标准库里的打开文件操作</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">use std::fs::File;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let f = File::open("hello.txt");</span><br><span class="line"> let f = match f{</span><br><span class="line"> Ok(file) => file,</span><br><span class="line"> Err(error) => {</span><br><span class="line"> panic!("There was a problem opening the file: {:?}", error)</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>File::open</code>函数回返回一个<code>Result</code>类型,所以我们需要用<code>match</code>对<code>f</code>进行额外的处理,取出<code>Result</code>里的值,才能正确的获得打开文件,这迫使程序员必须用match枚举所有可能性,不然就无法正常编译~</p><h2 id="处理不同错误"><a href="#处理不同错误" class="headerlink" title="处理不同错误"></a>处理不同错误</h2><p>文件打开可能有多种错误,可能是文件不存在,可能是没有文件的读权限,那我们怎么从<code>Err</code>里分辨多种错误呢?</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">use std::fs::File;</span><br><span class="line">use std::io::ErrorKind;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let f = File::open("hello.txt");</span><br><span class="line"> let f = match f{</span><br><span class="line"> Ok(file) => file,</span><br><span class="line"> Err(error) => {</span><br><span class="line"> match error.kind() {</span><br><span class="line"> ErrorKind::NotFound => {</span><br><span class="line"> match File::create("hello.txt") {</span><br><span class="line"> Ok(fc) => fc,</span><br><span class="line"> Err(error) => panic!("Problem creating the file: {:?}", error)</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> other_error => panic!("Problem opening the file: {:?}", other_error)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码多了不少东西,我们一个一个来说</p><ol><li>通过<code>use</code>引入了<code>io</code>标准库的标准<code>io</code>相关错误类型<code>ErrorKind</code></li><li><code>error</code>错误类型的值可以通过 <code>.kind</code> 函数获取它的错误类型,错误类型也是一个枚举类型</li><li>所以可以通过 match 匹配错误类型,我们针对找不到文件的类型作了特殊处理——创建一个文件(创建文件的同时也要处理创建是否成功的Result类型),对于其他类型不知道怎么处理,就调用 <code>panic!</code></li></ol><p>可以看见,写一段 rust 代码突然变得繁琐起来了,本来我们只要几行代码就搞定的东西,Rust 却逼我们写了一堆 match 来处理报错,可读性也损失了不少。</p><p>书上提到了 Rust 提供了一个闭包的特性解决这个问题,简化代码,增加错误处理的可读性,以下是一个代码示例。但具体在后面讲解闭包的时候再作解释。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">use std::fs::File;</span><br><span class="line">use std::io::ErrorKind;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let f = File::open("hello.txt").map_err(|error|{</span><br><span class="line"> if error.kind() == ErrorKind::NotFound{</span><br><span class="line"> File::create("hello.txt").unwrap_or_else(|error|{</span><br><span class="line"> panic!("Problem creating the file: {:?}", error);</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> else{</span><br><span class="line"> panic!("Problem opening the file: {:?}", error);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="快捷处理方式"><a href="#快捷处理方式" class="headerlink" title="快捷处理方式"></a>快捷处理方式</h2><p>每次都写 match 和 panic! 来处理实在太麻烦了,Rust 当然也提供了一些快捷方式——<code>expect</code>和<code>unwrap</code></p><h3 id="unwrap"><a href="#unwrap" class="headerlink" title="unwrap"></a>unwrap</h3><p><code>unwarp</code>可以在返回的是 Ok 时直接返回值,是 Err 时直接自动帮你触发 panic! 报错。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">use std::fs::File;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let f = File::open("hello2.txt").unwrap();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>没有文件时,会直接触发报错</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }'</span><br></pre></td></tr></table></figure></div><h3 id="expect"><a href="#expect" class="headerlink" title="expect"></a>expect</h3><p><code>expect</code>的效果和<code>unwarp</code>是差不多的,不同点在于<code>expect</code>可以让程序员自己指定一个报错的提示信息</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">thread 'main' panicked at 'Failed to open hello2.txt: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }', src\main.rs:4:38</span><br></pre></td></tr></table></figure></div><p>可以看见 paniked at xxx 的地方发生了变化</p><h2 id="错误传播"><a href="#错误传播" class="headerlink" title="错误传播"></a>错误传播</h2><p>上面提到了可能执行失败的函数会返回一个错误类型,那我们自己写的函数怎么返回呢?以下是一个例子</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">use std::fs::File;</span><br><span class="line">use std::io;</span><br><span class="line">use std::io::Read;</span><br><span class="line"></span><br><span class="line">fn my_function() -> Result<String, io::Error>{</span><br><span class="line"> let f = File::open("hello.txt");</span><br><span class="line"> let mut f = match f {</span><br><span class="line"> Ok(file) => file,</span><br><span class="line"> Err(e) => return Err(e),</span><br><span class="line"> };</span><br><span class="line"> let mut s = String::new();</span><br><span class="line"> match f.read_to_string(&mut s){</span><br><span class="line"> Ok(_) => Ok(s),</span><br><span class="line"> Err(e) => Err(e),</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">fn main() {</span><br><span class="line"> my_function().unwrap();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们定义了一个函数,返回类型是 Result 枚举,其中两个关于Ok和Err的泛型参数被我们规定为了字符串和标准io错误(这里用标准io错误是因为我们函数里相关的错误都是io错误,当然你可以自己定义其他错误,作一个额外处理然后返回)。</p><p>函数里首先对 hello.txt 文件进行读取,读取成功的话取出 Ok 里的文件句柄 file,失败的话则用 <code>return</code> 关键字提前结束函数,返回 Err 类型,然后读取文件内容,存到字符串里,同时也要处理是否读取成功,最后一个 match 表达式不写分号即可直接返回值,不用 return。</p><blockquote><p>PS: 这里有个小细节,f 必须是可变的才能读取内容。为什么呢?我只读文件没有修改文件内容啊。因为这里读取到的 f 只是一个句柄,而我们真正读取内容的时候,需要修改偏移量什么的吧。因此需要可变。</p></blockquote><h3 id="?运算符"><a href="#?运算符" class="headerlink" title="?运算符"></a>?运算符</h3><p>因为错误传播太常见了,所以这一块有着一点语法糖——<code>?</code>运算符</p><p>我们直接看一段例子</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn my_function() -> Result<String, io::Error>{</span><br><span class="line"> let mut f = File::open("hello.txt")?;</span><br><span class="line"> let mut s = String::new();</span><br><span class="line"> f.read_to_string(&mut s)?;</span><br><span class="line"> Ok(s)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>是不是感觉瞬间干净了不少</p><p><code>?</code>运算符的工作和我们之前的match是差不多的,首先,<strong>它只能用于Reult类型的后面,且只能用于返回值的Result类型的函数</strong>。在值为Ok的时候,它会把Ok的值返回作为这个表达式的结果。在值为Err的时候,它会调用return把错误类型作为整个程序的返回。</p><p>最后我们手动构建了一个Result的Ok变体用于运行成功的返回。</p><p>但<code>?</code>运算符也和match表达式有一点点不同。那就是它对于错误类型,也就是<code>Err(e)</code>里的<code>e</code>,会偷偷调用一次<code>from</code>函数,把错误类型转换成我们函数所返回的错误类型,这对于我们有不同类型的错误,但函数返回的类型只有一种时很有用,如果是match还需要我们手动进行额外处理。</p><p>当然,我们还能把代码写的更短,在实际项目中,给文件句柄赋予一个 f 变量其实也是多余的</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fn my_function() -> Result<String, io::Error>{</span><br><span class="line"> let mut s = String::new();</span><br><span class="line"> File::open("hello.txt")?.read_to_string(&mut s)?;</span><br><span class="line"> Ok(s)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h1 id="各种报错方法的使用场景"><a href="#各种报错方法的使用场景" class="headerlink" title="各种报错方法的使用场景"></a>各种报错方法的使用场景</h1><h2 id="关于panic-和Result"><a href="#关于panic-和Result" class="headerlink" title="关于panic!和Result"></a>关于panic!和Result</h2><p>调用 panic! 代表程序已经无法从这个错误中恢复了,所以选择自行了解。所以当你觉得在某种情况下无论如何也恢复不了,就可以代替使用这个函数的人直接 panic!,不然大多时候都可以选择返回一个 Result,交由使用者自己决定是不是 panic!</p><p>当然,还有另外一种情况,那就是用户的操作违反了你所编写的一些程序的“约定”,且这个非法操作会破坏你代码的一些原有行为,难以恢复,又或者是可能会触及到你代码的一些安全漏洞,这时候就可以选择直接 panic! 以终止用户的非法行为。例如数组访问越界,防止用户因为数组访问越界,访问到不属于这个数组的内存数据,以此造成一些安全问题的时候,我们通常会直接 panic!</p><h2 id="关于expect和unwrap"><a href="#关于expect和unwrap" class="headerlink" title="关于expect和unwrap"></a>关于expect和unwrap</h2><h3 id="对于早期开发和测试"><a href="#对于早期开发和测试" class="headerlink" title="对于早期开发和测试"></a>对于早期开发和测试</h3><p>使用 expect 和 unwrap 处理错误固然方便,但也代表我们失去了处理不同错误的机会,因为它会帮助我们直接把程序 panic! 掉。</p><p>所以一般情况下,我们都会把 expect 和 unwrap 当成是一个占位符,告诉后面,这里有个错误需要处理,但在开发的早期和需要测试的时候,我们为了方便会选择直接把程序 panic! 掉,既提高了开发效率,也可以方便测试,毕竟程序都直接崩溃了,这里的bug你总不能忽视了吧?快修bug去。</p><p>当然在后期交付用户的时候,为了用户友好性,当然不能一点小错误就直接 panic!,这时候我们就可以一个一个的查询之前留下的 expect 和 unwrap,去处理更细致的报错。</p><h3 id="对于你确定不会发生报错的时候"><a href="#对于你确定不会发生报错的时候" class="headerlink" title="对于你确定不会发生报错的时候"></a>对于你确定不会发生报错的时候</h3><p>在某些时候,你确定100%不可能会出现 Err 变体,那当然也能直接用 unwrap 节省代码量。例如</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">use std::net::IpAddr;</span><br><span class="line">fn main() {</span><br><span class="line"> let home: IpAddr = "127.0.0.1".parse().unwrap();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>127.0.0.1</code> 总不可能是个非法IP地址了吧?这还要我去处理各种报错就有点不合理了。</p><hr><p>以上,就是错误处理的全部内容了,大致总结一下就是</p><ol><li>panic! 和 Result——硬处理和软处理的方式</li><li>Result处理的优化——expect, unwrap 和 ?运算符</li><li>错误类型——error.kind()</li><li>一些基本的错误处理场景原则</li></ol><p>第10章开始就是 trait,泛型和生命周期了。</p>]]></content>
<summary type="html">
<div class="note-large default">
<div class="notel-title rounded-t-lg p-3 font-bold text-lg flex flex-row gap-2 items-center">
</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】8. 通用集合类型</title>
<link href="https://twosix.page/2023/04/02/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%918-%E9%80%9A%E7%94%A8%E9%9B%86%E5%90%88%E7%B1%BB%E5%9E%8B/"/>
<id>https://twosix.page/2023/04/02/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%918-%E9%80%9A%E7%94%A8%E9%9B%86%E5%90%88%E7%B1%BB%E5%9E%8B/</id>
<published>2023-04-02T14:51:57.000Z</published>
<updated>2024-07-04T15:52:28.395Z</updated>
<content type="html"><![CDATA[<h1 id="动态数组"><a href="#动态数组" class="headerlink" title="动态数组"></a>动态数组</h1><h2 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">let a:Vec<i32> = Vec::new();</span><br></pre></td></tr></table></figure></div><p>定义非常简单,是<code>Vec</code>的格式,<code>Vec</code> 也就是 vector,动态数组类型的关键字,用两个尖括号括住动态数组所存放的数据类型 T,代码里就是存放 i32 类型的数据。再使用<code>new</code>方法分配一片空间。</p><p>上面是一个创建一个指定类型的空数组,因为是空数组,所以编译器没法推理出我们数组的类型,所以要显示定义类型,如果给定数据,就不需要显示指定类型了,如下</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">let b = vec![1,2,3];</span><br></pre></td></tr></table></figure></div><p>这里我们用到了Rust官方提供的宏<code>vec!</code>用来创建一个动态数组,用方括号给定初始值,因为给的都是整数值,所以编译器会默认推理得 b 是 i32 类型的动态数组</p><blockquote><p>动态数组的所有权:动态数组的所有元素的所有权和变量绑定,当变量被销毁时,所有元素被销毁</p></blockquote><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="末尾添加元素"><a href="#末尾添加元素" class="headerlink" title="末尾添加元素"></a>末尾添加元素</h3><p>使用<code>push</code>方法即可在末尾添加元素</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut a:Vec<i32> = Vec::new();</span><br><span class="line"> a.push(1);</span><br><span class="line"> a.push(2);</span><br><span class="line"> println!("{:?}", a);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="读取元素"><a href="#读取元素" class="headerlink" title="读取元素"></a>读取元素</h3><p>Rust 提供了两种 vec 的访问方法</p><ol><li><code>[]</code> 运算符访问</li><li><code>get()</code> 方法访问</li></ol><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">let e1 = &a[0];</span><br><span class="line">println!("{:?}", e1);</span><br><span class="line">let e2 = a.get(1);</span><br><span class="line">match e2{</span><br><span class="line"> Some(e) => println!("{}", e),</span><br><span class="line"> None => println!("None"),</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>e1 通过下标访问获取 a 中的元素,而 e2 则是通过 get 方法访问获取 a 的元素,二者主要有以下区别:</p><ol><li><code>&</code>和<code>[]</code>会返回元素的引用</li><li><code>get</code>会返回一个<code>Option<&T></code>类型</li></ol><p>所以作用也很明显了,get 方法可以有效避免访问越界的问题,当你访问了一个不存在的元素时,它会给你返回一个 None 值,而 [] 则不会,若访问越界,会直接导致程序崩溃。两者可以视情况使用。</p><p>值得一提的是,这里使用引用借用了一下数组的元素,而之前我们说过,不可变引用不能和可变引用一起定义,所以当我们<strong>借用了数组元素后,是没有办法向数组末尾添加元素的</strong>。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">let mut a = vec![1, 2, 3];</span><br><span class="line">let e1 = &a[0];</span><br><span class="line">a.push(4);</span><br><span class="line">println!("{:?}", e1);</span><br></pre></td></tr></table></figure></div><p>因为push的时候,相当于传入了一个可变的引用,用于修改数组,而之前已经定义了一个不可别引用,两者无法同时存在,所以编译器会报错。</p><p>可能会有人问,我只是引用了数组的第一个元素而已,和插入的元素有什么关系?</p><p>这是因为动态数组本质上还是存储在一片堆上的连续空间,正因为是连续的,所以你变动了前面的元素的时候,就有可能会影响到后面的元素,这就是另类的数据竞争,Rust 的所有权机制杜绝了这种情况的发生(同理,你后面不会用到 e1,不存在修改前面数据情况的话,其实是可以编译通过的)</p><blockquote><p>PS:</p><p>书上只提到了借用数组元素的访问方法,但实际上不用借用也是可以访问的,会通过创建一个副本的方式来进行返回</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut a = vec![1, 2, 3];</span><br><span class="line"> let e = a[0]; // 返回的是值的副本</span><br><span class="line"> a.push(4); // 可以传入可变引用来修改数组</span><br><span class="line"> println!("{}", e);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>记得我们在前面提过,基本类型都是存储在栈里的,所以默认赋值方式都是深拷贝返回副本。而存储在堆里的数据就不一样了,默认都是浅拷贝,所以当我们使用字符串的时候就没办法不借用访问了,编译器会报错提示我们需要使用<code>copy</code>方法,手动拷贝。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut a = vec![String::from("hello"), String::from("world")];</span><br><span class="line"> let e = a[0]; // 需要返回拷贝,但默认赋值不拷贝,报错。</span><br><span class="line"> a.push(String::from("!!!"));</span><br><span class="line"> println!("{}", e);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div></blockquote><h3 id="遍历数组"><a href="#遍历数组" class="headerlink" title="遍历数组"></a>遍历数组</h3><p>我们可以通过<code>for</code>循环来遍历动态数组</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let a = vec![1, 2, 3];</span><br><span class="line"> for i in &a {</span><br><span class="line"> println!("{}", i);</span><br><span class="line"> }</span><br><span class="line"> let mut b = a;</span><br><span class="line"> for i in &mut b{</span><br><span class="line"> *i += 5;</span><br><span class="line"> }</span><br><span class="line"> println!("{:?}", b);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol><li>对于不可变的动态数组<code>a</code>,我们用了不可变引用来遍历数组,并打印输出</li><li>对于可变数组<code>b</code>,我们用了可变引用来遍历数组,再进行解引用访问到对应的值,把他们每一个值都+5,再输出数组<code>b</code></li></ol><blockquote><p>值得一提的是,这里遍历不用<code>iter()</code>方法,估计是通过某种方法内置了</p></blockquote><h2 id="用枚举类型让动态数组存储多个类型的值"><a href="#用枚举类型让动态数组存储多个类型的值" class="headerlink" title="用枚举类型让动态数组存储多个类型的值"></a>用枚举类型让动态数组存储多个类型的值</h2><p>一个动态数组只能存储一个类型的值,那我们需要存储多个类型的时候怎么办?记得之前介绍枚举类型的时候,我们提到过可以利用枚举类型,方便的向函数传入多个类型的参数,,那么我们可以利用一下这个特性,用于在动态数组里存储多个类型的值。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">enum MyType{</span><br><span class="line"> Int(i32),</span><br><span class="line"> Float(f32),</span><br><span class="line"> Text(String),</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let a = vec![MyType::Int(1), MyType::Float(2.0), MyType::Text(String::from("Hello"))];</span><br><span class="line"> let e = &a[0];</span><br><span class="line"> match e {</span><br><span class="line"> MyType::Int(value)=> println!("Int: {}", value),</span><br><span class="line"> MyType::Float(value)=> println!("Float: {}", value),</span><br><span class="line"> MyType::Text(value)=> println!("Text: {}", value),</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>在这个例子里我们就把三个类型的值都定义为同一个枚举类型的值,让动态数组能够方便的存储。</p><p>同时,因为是枚举类型,所以理所当然的我们就需要在访问的时候穷举所有可能性。</p><h1 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h1><p>我们已经在代码里用过无数次的<code>String</code>了,在这一节里,我们就需要更加深入的理解以下Rust里的字符串原理。</p><h2 id="什么是字符串"><a href="#什么是字符串" class="headerlink" title="什么是字符串"></a>什么是字符串</h2><p>Rust 核心部分只有一种字符串类型:字符串切片<code>str</code>,通常以<code>&str</code>的形态出现,用于指向别人的一段UTF-8编码的字符串引用</p><p>而我们常用的字符串<code>String</code>是实现在标准库里的。但这两种字符串的应用都非常广泛,我们既需要一个结构来存储完整的字符串,也很经常需要用字符串切片来引用其中一段字符串,人们一般把这两种类型都称作字符串,毕竟他们表现出来的样子就是一个人类所理解的字符串。另外再强调一下,Rust的字符串编码是<strong>UTF-8</strong></p><p>当然标准库里还有很多乱七八糟的字符串,比如<code>OsString</code>,<code>OsStr</code>、<code>CString</code>、<code>CStr</code>,后面的结尾是<code>String</code>和<code>Str</code>也表明了这个结构到底是所有权版本还是借用的版本,这些都是不同编码或者不同内存布局的字符串,书上没讲好像也不会讲,感兴趣可以自行查询官方API文档学习。</p><h2 id="字符串的使用"><a href="#字符串的使用" class="headerlink" title="字符串的使用"></a>字符串的使用</h2><p>很多在<code>Vec</code>能用的方法在<code>String</code>也能用</p><h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><p>首先我们就可以使用<code>new</code>的方法来创建空字符串:let s = String::new();</p><p>对于有初始值的情况,我们也可以用我们所熟知的<code>String::from()</code>,也可以用<code>to_string</code>的方法。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">let data = "hello, world!"; // 创建一个字符串切片 &str</span><br><span class="line">let s = data.to_string(); // 把&str转为String类型,获取所有权</span><br></pre></td></tr></table></figure></div><p>这里我比较疑惑的是,用双引号定义的数据值在没有用to_string的时候所有权归谁?在什么时候被回收?书上说的是,这种类型叫<code>Display trait</code>,但没有详细说明,暂且放下。</p><p><code>from</code>和<code>to_string</code>的效果是一样的,可以根据个人喜好使用。</p><h3 id="更新"><a href="#更新" class="headerlink" title="更新"></a>更新</h3><p>使用<code>push</code>或<code>push_str</code>都能向字符串的末尾添加内容,其中<code>push</code>是添加字符,而<code>push_str</code>是添加字符串</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut s = String::new();</span><br><span class="line"> s.push_str("hello world");</span><br><span class="line"> s.push('!'); // 单引号表示字符</span><br><span class="line"> println!("{}", s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>另外,<code>push_str</code>和<code>push</code>都是不取得所有权的,所以我们可以传入字符串切片就能实现添加,同时如果传入其他变量的话,也不会使得其他字符串失效。</p><p>同时,我们也可以使用<code>+</code>运算符来实现字符串的拼接</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s1 = String::from("hello");</span><br><span class="line"> let s2 = String::from(" world");</span><br><span class="line"> let s3 = s1 + &s2;</span><br><span class="line"> println!("{}", s3);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>但需要注意的是,<code>+</code>运算符只能实现<code>String</code>和<code>&str</code>的拼接,也就是说以上代码会<strong>夺取</strong><code>s1</code>的所有权,然后返回给<code>s3</code>,然后<code>s1</code>就不能再使用了,<code>+</code>运算符的大概定义是这样的:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fn add(self, &str)->String{}</span><br></pre></td></tr></table></figure></div><p>所以<code>+</code>运算符似乎看起来用着不是很方便,首先1. 所有权会有影响,2. 在多字符串拼接的时候其实不是很方便。</p><p>所以我们还有一个宏函数,<code>format!</code></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s1 = String::from("hello");</span><br><span class="line"> let s2 = String::from(" world");</span><br><span class="line"> let s3 = format!("{}{}", s1, s2);</span><br><span class="line"> println!("{}", s3);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>format!</code>和<code>println!</code>的用法是一样的,不同的是println是格式化输出到屏幕上,format是格式化输入到变量里存起来,而且<strong>format不会夺取任何变量的所有权</strong>。</p><h2 id="访问字符串"><a href="#访问字符串" class="headerlink" title="访问字符串"></a>访问字符串</h2><h3 id="为什么不能索引访问?"><a href="#为什么不能索引访问?" class="headerlink" title="为什么不能索引访问?"></a>为什么不能索引访问?</h3><p>大部分语言都是支持通过<code>[]</code>运算符,利用索引访问字符串的,我们可以试试</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s1 = String::from("hello");</span><br><span class="line"> let s = s1[0];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>报错:<code>error[E0277]: the type String cannot be indexed by {integer}</code> 很简单,就是说String不支持索引访问。</p><p>至于为什么,就要从Rust实现String的底层来解释了。</p><p>我们可以先来看一下这个例子</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let len1 = String::from("hello").len();</span><br><span class="line"> let len2 = String::from("你好").len();</span><br><span class="line"> println!("len1: {}, len2: {}", len1, len2);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码里<code>len1</code>的输出值是5,这很正常,每个英文字母占用1个字节,代表一个位置,但<code>len2</code>却输出的是6,每个字符占用了3个字节,也就是3个位置,这意味着我们并不能通过索引,访问到我们真正想访问到的字符。</p><p>因为在Rust里,String其实是通过一个<code>Vec</code>的动态数组来实现的,其中u8就是对应着UTF-8编码的字节值。一个Unicode字符可以由多个UTF-8编码来表达,所以当用户访问索引0的时候,得到的只是“你”的编码的其中一部分,而不是完整“你”,是一个没有任何含义的整数值,这是没有意义的。所以为了避免返回一个不是用户期待的值,所以Rust禁用了索引访问,不进行编译。</p><p>当然,还有另外一个禁用索引访问的理由,那就是用户往往觉得索引访问的时间复杂度理所当然是O(1),但在字符串里,就需要视情况而定,不能保证,所以不用也好。</p><p>同理还有字符串切片</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">let s = String::from("你好");</span><br><span class="line">println!("{}", &s[0..3]);</span><br></pre></td></tr></table></figure></div><p>我们之前知道了你好里面每个字符占3个字节,所以我们可以通过一次性切3个字节,来得到一个“你”,那我是不是可以通过曲线救国的方式,利用切片来实现索引访问0?很不幸,Rust也掐死了这一条路,当你视图使用<code>&s[0..1]</code>的方式访问索引0时,程序会发生崩溃,告诉你不能这么写。</p><h3 id="怎么访问字符串?"><a href="#怎么访问字符串?" class="headerlink" title="怎么访问字符串?"></a>怎么访问字符串?</h3><p>既然不能索引访问,那我们究竟要怎么访问字符串呢?</p><ol><li><code>chars()</code>方法</li></ol><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s = String::from("你好");</span><br><span class="line"> for c in s.chars() {</span><br><span class="line"> println!("{}", c);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>chars</code>方法能把字符串里的字节值凑成一个<code>char</code>类型的值再作为一个结果数组返回给你,这样你就可以放心的访问得到你所希望的字符了。</p><ol start="2"><li><code>bytes()</code> 方法</li></ol><p>可能就有人说了,那我就是想访问到索引0,我想知道这里存的字节值是什么!放心,也有办法,<code>bytes()</code> 方法就可以把字符串转成字节值的数组,让你访问到每一个字节值</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s = String::from("你好");</span><br><span class="line"> for c in s.bytes() {</span><br><span class="line"> println!("{}", c);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>总的来说,Rust为了考虑字符串中的使用安全,把很多字符串内部实现的复杂性给暴露了出来,你不得不去考虑更多底层的东西。这也是一个权衡,为了安全,你不得不付出一些便利性的代价。</p><h1 id="哈希映射"><a href="#哈希映射" class="headerlink" title="哈希映射"></a>哈希映射</h1><p>哈希也是一个非常常用的数据结构了,它存储了一个键(Key)到值(Value)的映射关系,很多时候我们并不满足于普通数组的索引下标-值的映射关系,这时候就可以用哈希映射。</p><h2 id="创建和使用"><a href="#创建和使用" class="headerlink" title="创建和使用"></a>创建和使用</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">use std::collections::HashMap;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let mut scores = HashMap::new();</span><br><span class="line"> scores.insert(String::from("blue"), 1);</span><br><span class="line"> scores.insert(String::from("red"), 2);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>哈希映射(HashMap)被定义在标准库的<code>collections</code>中,不是默认导入的,所以我们要用<code>use</code>来引入。</p><p>我们通过常规的<code>new</code>方法建立了一个哈希映射,然后通过<code>insert</code>方法插入了一对映射关系,其中第一个值是key,第二个值value,这里的意思就是,我们建立了一个比分映射关系,key是队伍,用字符串代表红队蓝队,value是值,代表比分。建立这么一个映射关系,我们就可以很轻松的知道队伍的对应得分。</p><p>值得一提的是,<strong>哈希映射也要求所有的键是一个类型,所有的值是一个类型</strong></p><p>我们也可以通过动态数组来作为初始值构建哈希映射</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">use std::collections::HashMap;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let team = vec![String::from("blue"), String::from("red")];</span><br><span class="line"> let score = vec![1, 2];</span><br><span class="line"> let scores: HashMap<_,_> = team.iter().zip(score.iter()).collect();</span><br><span class="line"> println!("{:?}", scores);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码里,我们首先定义了一个动态数组存储队伍,一个动态数组存储比分。然后我们使用<code>zip</code>方法,把队伍和比分创建一个元组一一对应起来,这里得到的结果大概是这样的:<code>[(blue, 1), (red, 2)]</code>,然后,我们再通过<code>collect()</code>方法,以第一个值为key,第二个值为value,把这么一个元组数组转成哈希映射。</p><p>这里需要显示的给出类型,因为<code>collect</code>方法可以作用于不同的数据结构,不止是哈希表,所以我们要告诉编译器,这里我们是<code>collect</code>成了一个哈希表,但对于哈希表内的元素类型,我们可以写成通配符,让编译器去推理得到。</p><h2 id="哈希映射的所有权"><a href="#哈希映射的所有权" class="headerlink" title="哈希映射的所有权"></a>哈希映射的所有权</h2><p>和之前说的差不多,对于基本类型这种存储在栈上的数据,会深复制一份进入哈希映射,而存储在堆上的数据,像字符串,会把所有权一并传入哈希映射,导致原来的变量被销毁。</p><p>当然我们可以把字符串的引用传进哈希映射,但这种做法就需要保证哈希映射的作用域结束之前字符串是一直有效的。这一部分的知识需要到后面的生命周期部分详细解释。</p><h2 id="访问元素"><a href="#访问元素" class="headerlink" title="访问元素"></a>访问元素</h2><p>和动态数组的访问一样,我们可以通过<code>[]</code>访问,也可以通过<code>get</code>访问,里面不同点和需要注意的地方也是相同的。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let teama = String::from("blue");</span><br><span class="line"> let mut scores = HashMap::new();</span><br><span class="line"> scores.insert(teama, 1);</span><br><span class="line"> scores.insert(String::from("red"), 2);</span><br><span class="line"> println!("{:?}", scores["blue"]);</span><br><span class="line"> match scores.get("blue") {</span><br><span class="line"> Some(&score) => println!("blue team score is {}", score),</span><br><span class="line"> None => println!("blue team score is not found"),</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>遍历访问同理</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let teama = String::from("blue");</span><br><span class="line"> let mut scores = HashMap::new();</span><br><span class="line"> scores.insert(teama, 1);</span><br><span class="line"> scores.insert(String::from("red"), 2);</span><br><span class="line"> for (key, value) in &scores { //注意借用,不然for完所有权被没收了</span><br><span class="line"> println!("{}: {}", key, value);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="更新-1"><a href="#更新-1" class="headerlink" title="更新"></a>更新</h2><h3 id="覆盖旧值"><a href="#覆盖旧值" class="headerlink" title="覆盖旧值"></a>覆盖旧值</h3><p>哈希映射的一个键只能对应一个值,所以当我们往哈希映射里插入已有的键值,会覆盖原有的键的值。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">use std::collections::HashMap;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let teama = String::from("blue");</span><br><span class="line"> let mut scores = HashMap::new();</span><br><span class="line"> scores.insert(teama, 1);</span><br><span class="line"> scores.insert(String::from("blue"), 2); // 把原本blue的1覆盖为2</span><br><span class="line"> println!("{:?}", scores);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>有时我们不想覆盖掉旧值,只想在没有对应的key的时候才插入数据,这时候可以用<code>entry</code>方法</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">use std::collections::HashMap;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let teama = String::from("blue");</span><br><span class="line"> let mut scores = HashMap::new();</span><br><span class="line"> scores.insert(teama, 1);</span><br><span class="line"> scores.entry(String::from("blue")).or_insert(3);</span><br><span class="line"> scores.entry(String::from("red")).or_insert(3);</span><br><span class="line"> println!("{:?}", scores);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>entry</code>方法会返回一个<code>Entry</code>的枚举,表面这个键的值是否存在,<code>or_insert</code>方法返回Entry所指向的值的引用,如果值不存在,就把传入的值插入到哈希映射里,返回这个值的引用。</p><p>知道了<code>or_insert</code>后,我们就可以利用它做一些更灵活的应用。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">use std::collections::HashMap;</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let text = String::from("aaaabbbbbcc");</span><br><span class="line"> let mut char_count = HashMap::new();</span><br><span class="line"> for c in text.chars() {</span><br><span class="line"> let count = char_count.entry(c).or_insert(0);</span><br><span class="line"> *count += 1;</span><br><span class="line"> }</span><br><span class="line"> println!("{:?}", char_count);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码就是简单的数字符串里的英文字母有多少个。我们利用entry+or_insert在字母不存在的时候,赋初值为0,然后对这个访问到的字母加1次数。之前说了,<code>or_insert</code>会返回一个值的引用,所以我们可以利用这个返回,轻松的访问到对应的值的空间,只需要一次解引用就可以了。并且这个可变引用会在for循环结尾就离开作用域,也满足安全的规则</p><blockquote><p>值得一提的是,似乎不能通过索引的方式对哈希表进行值更改,但报错提示里提到了<code>get_mut()</code>方法,书上没说,或许感兴趣可以后续看看。</p></blockquote><h2 id="所使用的哈希函数"><a href="#所使用的哈希函数" class="headerlink" title="所使用的哈希函数"></a>所使用的哈希函数</h2><p>Rust 默认使用的是一个比较安全的哈希加密算法,但并不是最高效的一个算法,如果你觉得这样效率太低,当然你也可以通过 trait 的方式使用自己的哈希算法,这会在后续的章节里有讲。</p><hr><p>结束!这一章里面主要就是学了动态数组/字符串/哈希映射三个集合结构,介绍了一下基本的使用,还有他们的所有权规则,为了满足这个所有权,很多结构的使用方式都变得有点麻烦了,放python里,基本都是一个索引就能解决的事,在这里要考虑一大堆所有权问题,肉眼可见的麻烦起来了。</p><p>下一节学错误处理,学完就到trait和生命周期了,搞完这些个rust独有的概念,也就差不多可以上手使用了。</p>]]></content>
<summary type="html"><h1 id="动态数组"><a href="#动态数组" class="headerlink" title="动态数组"></a>动态数组</h1><h2 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】7. 包、单元包和模块</title>
<link href="https://twosix.page/2023/04/01/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%917-%E5%8C%85%E3%80%81%E5%8D%95%E5%85%83%E5%8C%85%E5%92%8C%E6%A8%A1%E5%9D%97/"/>
<id>https://twosix.page/2023/04/01/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%917-%E5%8C%85%E3%80%81%E5%8D%95%E5%85%83%E5%8C%85%E5%92%8C%E6%A8%A1%E5%9D%97/</id>
<published>2023-04-01T14:41:12.000Z</published>
<updated>2024-07-04T15:52:28.393Z</updated>
<content type="html"><![CDATA[<h1 id="包与单元包"><a href="#包与单元包" class="headerlink" title="包与单元包"></a>包与单元包</h1><ol><li>单元包(Crate):单元包可以被用来生成二进制的程序或者库;单元包的入口文件称为单元包的入口节点</li><li>包(Package):一个包由一个或多个单元包集合而成,用 Cargo.toml 描述包内的单元包怎么构建;一个包最多也拥有一个<strong>库单元包</strong>;包可以有多个二进制单元包;包必须至少拥有一个单元包,可以是库单元包,也可以是二进制单元包。</li></ol><p>举个例子,我们一直用的<code>cargo new test</code>命令就是用来生成一个名为 test 包的,其中我们的代码文件 <code>src/main.rs</code> 就是 test 包下的一个单元包,叫 main,代码文件就是这个单元包的根节点,这类代码编译后可以生成一个二进制可执行文件,所以也叫二进制单元包。我们也可以通过不断的在 <code>src</code> 目录下写代码,创建更多的二进制单元包。</p><p>书上并没有介绍我更关心的库单元包,只是它举了个文件名例子<code>src/lib.rs</code>。后面再看。</p><p>同时,每个单元包内的代码都有自己的作用域(和 c++ 的命名空间相当),用来避免命名冲突。也就是我们之前 use 了 rand 包后,需要指定<code>rand::Rng</code>才能使用<code>Rng</code>模块一样。这样可以避免 Rng 这个名字和你自己定义的 Rng 结构冲突,你可以更随心所欲的命名。</p><h1 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h1><p>模块可以提供私有/公共的权限管理功能,也就是熟知的 private 和 public</p><h2 id="模块的定义"><a href="#模块的定义" class="headerlink" title="模块的定义"></a>模块的定义</h2><p>我们现在 <code>src</code> 文件夹下创建一个<code>lib.rs</code>文件,新建一个库单元包,然后再编写代码</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">mod front_of_house{</span><br><span class="line"> mod hosting{</span><br><span class="line"> fn add_to_waitlist(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn seat_at_table(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> mod serving{</span><br><span class="line"> fn take_order(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn serve_order(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn take_payment(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>代码解释</p><ol><li><code>mod</code>关键字:mod 关键字用来定义一个模块,我们定义了一个名为 front_of_house 的模块,用来管理餐厅的前厅部分,并且前厅部分又分为服务客户的服务员,处理订单的前台等,所以我们在模块下又定义了两个模块 hosting, serving</li><li>接着我们以模块来对代码进行分组,在不同模块下定义了对应的功能函数</li></ol><p>这段代码就相当于我们构建了一个单元包,单元包里包含了一个模块,模块内有两个模块,而两个模块又有多个函数,构成了一个树的层级结构。</p><p><img lazyload src="/images/loading.svg" data-src="https://twosix.page/wp-content/uploads/2023/04/Snipaste_2023-04-01_20-48-54.jpg" alt="包管理架构" ></p><h2 id="模块的调用"><a href="#模块的调用" class="headerlink" title="模块的调用"></a>模块的调用</h2><p>我们可以通过绝对路径和相对路径的方式来调用这个层级结构的某个函数。</p><p>Rust 通过 <code>::</code> 符号来构建访问路径,当我们想调用 hostting模块下的 add_to_waitlist 函数,可以用以下方式(这段代码是暂时编译不通过的)</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">mod front_of_house{</span><br><span class="line"> mod hosting{</span><br><span class="line"> fn add_to_waitlist(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn seat_at_table(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> mod serving{</span><br><span class="line"> fn take_order(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn serve_order(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn take_payment(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">pub fn eat_at_restaurant() {</span><br><span class="line"> crate::front_of_house::hosting::add_to_waitlist();</span><br><span class="line"> </span><br><span class="line"> front_of_house::hosting::add_to_waitlist();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol><li>绝对路径:<code>crate::front_of_house::hosting::add_to_waitlist();</code> 从指定的入口文件开始访问到函数</li><li>相对路径:<code>front_of_house::hosting::add_to_waitlist();</code> 用相对路径来访问当前函数同级下的函数,这里的<code>eat_at_restaurant</code>和<code>front_of_house</code>同级</li></ol><p>绝对路径和相对路径的优缺点也不用多说了吧,当可能需要同步移动两个模块的时候,相对路径好,单独移动一个模块的时候用绝对路径好。视情况而定就好</p><h2 id="访问权限"><a href="#访问权限" class="headerlink" title="访问权限"></a>访问权限</h2><p>刚说了上面的代码是编译不通过的,我们编译一下代码,看看为什么不通过,</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">error[E0603]: module `hosting` is private</span><br></pre></td></tr></table></figure></div><p>报错,hosting 是私有的</p><p>也就是说,<strong>Rust 里所有的条目,在没有特意声明的情况下,默认都是私有的</strong>,权限这块的规则大概是:父级模块无法访问私有的子模块条目,子模块可以访问父级模块的私有条目,同级之间可以互相访问。</p><p>但是注意,公有的模块不代表模块里的字段公有。例如</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">mod front_of_house{</span><br><span class="line"> pub mod hosting{</span><br><span class="line"> fn add_to_waitlist(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn seat_at_table(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> mod serving{</span><br><span class="line"> fn take_order(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn serve_order(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn take_payment(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">pub fn eat_at_restaurant() {</span><br><span class="line"> crate::front_of_house::hosting::add_to_waitlist();</span><br><span class="line"></span><br><span class="line"> front_of_house::hosting::add_to_waitlist();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们用<code>pub</code>关键字声明了<code>hosting</code>模块公有,但实际仍然会编译错误。因为这一次声明,只是声明了父级的 front_of_house 模块可以访问 hosting 模块了,但父级 hosting 模块却依旧不能访问它的私有字段 add_to_waitlist</p><p>也就是说,<strong>父级条目是不是公有的并不影响它内部的条目的公有或私有状态</strong></p><p>所以要代码能编译通过很简单,我们把 add_to_waitlist 也公开就行。</p><p>可能有人想问,为什么 front_of_house 没有公开也能访问?因为 front_of_house 和 eat_at_restaurant 是同级的,具有互相访问的权限。</p><h2 id="super-关键字"><a href="#super-关键字" class="headerlink" title="super 关键字"></a>super 关键字</h2><p><code>super</code> 关键字和python一样,用来查找到父模块的路径,属于相对路径的一种。应该不用多说,用一个代码来当例子吧</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">mod front_of_house{</span><br><span class="line"> pub mod hosting{</span><br><span class="line"> pub fn add_to_waitlist(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn seat_at_table(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> mod serving{</span><br><span class="line"> fn take_order(){</span><br><span class="line"> super::hosting::add_to_waitlist(); // 通过相对路径访问到add_to_waitlist</span><br><span class="line"> }</span><br><span class="line"> fn serve_order(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> fn take_payment(){</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="结构体和枚举类型的公有"><a href="#结构体和枚举类型的公有" class="headerlink" title="结构体和枚举类型的公有"></a>结构体和枚举类型的公有</h2><h2 id="结构体"><a href="#结构体" class="headerlink" title="结构体"></a>结构体</h2><p>结构体的权限和模块基本相同,也就是父级模块的公有,不影响子字段的公有或私有</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">mod back_of_house{</span><br><span class="line"> pub struct Breakfast{</span><br><span class="line"> pub toast: String,</span><br><span class="line"> seasonal_fruit: String,</span><br><span class="line"> }</span><br><span class="line"> impl Breakfast{</span><br><span class="line"> pub fn summer(toast: &str) -> Breakfast{</span><br><span class="line"> Breakfast{</span><br><span class="line"> toast: String::from(toast),</span><br><span class="line"> seasonal_fruit: String::from("peaches"),</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">pub fn eat_at_restaurant() {</span><br><span class="line"> let mut meal = back_of_house::Breakfast::summer("Rye");</span><br><span class="line"> meal.toast = String::from("Wheat");</span><br><span class="line"> println!("I'd like {} toast please", meal.toast);</span><br><span class="line"> println!("I'd like {} fruit please", meal.seasonal_fruit);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里,我们首先定义了一个后厨模块 <code>back_of_house</code>,定义了一个早餐结构体 <code>Breakfast</code>,包含吐司和水果两个变量,一个公有一个私有,然后实现了一个结构体内的函数 <code>summer</code>,用来创建一个包含指定吐司和水果 <code>Breakfast</code> 结构体。</p><p>后续,我们在 <code>eat_at_restaurant </code>通过相对路径创建了一个可变的 Breakfast 结构体,并试图访问结构体的字段。</p><p>通过观察报错就可以知道,公有的 <code>meal.toast</code> 可以正常的访问和修改,但私有的 <code>meal.seasonal_fruit</code> 无法访问,也就无从谈起修改了。</p><p>值得一提的是,因为 Breakfast 有一个私有字段,所以如果我们不定义一个子级的 summer 函数,我们甚至不能创建一个 Breakfast 结构体,因为我们没办法在外部给 seasonal_fruit 赋值。</p><h2 id="枚举类型"><a href="#枚举类型" class="headerlink" title="枚举类型"></a>枚举类型</h2><p>但枚举类型不一样,枚举公有后,所有字段都公有了,因为枚举类型这东西必须全公有才好用,一个公有一个私有,没有什么意义,例如说你 match 总要做个完整性检查吧?字段都不能全部完全访问,何谈完整的处理?所以一半私有一半公有是没有意义的。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">mod back_of_house{</span><br><span class="line"> pub struct Breakfast{</span><br><span class="line"> pub toast: String,</span><br><span class="line"> seasonal_fruit: String,</span><br><span class="line"> }</span><br><span class="line"> impl Breakfast{</span><br><span class="line"> pub fn summer(toast: &str) -> Breakfast{</span><br><span class="line"> Breakfast{</span><br><span class="line"> toast: String::from(toast),</span><br><span class="line"> seasonal_fruit: String::from("peaches"),</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> pub enum Appetizer{</span><br><span class="line"> Soup,</span><br><span class="line"> Salad,</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">pub fn eat_at_restaurant() {</span><br><span class="line"> let a = back_of_house::Appetizer::Soup;</span><br><span class="line"> let b = back_of_house::Appetizer::Salad;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们新加了一个枚举类型前菜 Appetizer,字段没有声明为公有,但依旧可以正常访问。</p><h1 id="use关键字"><a href="#use关键字" class="headerlink" title="use关键字"></a>use关键字</h1><h2 id="use的基本使用"><a href="#use的基本使用" class="headerlink" title="use的基本使用"></a>use的基本使用</h2><p>如果我们要多次调用模块里的函数,如果一直要写一大串的路径似乎有点麻烦。<code>use </code>关键字就是用来简化这个步骤的。原理就叫:引入作用域</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">// 还是前面的代码,前厅部分,为了美观省略了</span><br><span class="line">use crate::front_of_house::hosting;</span><br><span class="line"></span><br><span class="line">pub fn eat_at_restaurant() {</span><br><span class="line"> hosting::add_to_waitlist();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们就用 use 关键字,用绝对路径的方法把 hosting 这个字段引入了当前作用域,这样 hosting 就可以作为本地的字段,直接使用就行了,当然,这样引入后自然也会和本地的 hosting 字段冲突,你不能再定义一个叫 hosting 的东西了。</p><p>或许有人想问 use 相对路径行不?可以,可以自己试试。</p><blockquote><p>PS:在书上的版本还需要在相对路径前加入 <code>self</code> 字段来指定当前所在的作用域,但它提到了有些开发者在视图去掉这个字段。我的版本下没加也编译通过了,看来他们成功了。</p></blockquote><p>后面书上还提到了一个代码规范,虽然我不会这么写,但似乎 csdn 的博客上有不少人确实会这样喜欢省事,我简单拉出来提一下。</p><p>有人可能会问,上面的代码为什么只 use 到了 hosting,既然只用 add_to_waitlist 函数为什么不直接 use 到 add_to_waitlist 函数呢?</p><p>原因很简单,我们需要告诉所有看这段代码的人,当然也包括自己,这个函数并不是在本地定义的,而是引用了哪里的一个包/模块里面的函数,既增加了一定的可读性,也防止了一部分同名。毕竟很多最底层的字段命名,一般都是十分通用的名字。这是一个好习惯。</p><h2 id="pub-use"><a href="#pub-use" class="headerlink" title="pub use"></a>pub use</h2><p>use 字段通常是以私有的方式,引入当前作用域,也就是说,你引入之后,也只是在当前 use 的作用域生效,在其地方是不生效的,依旧要通过路径访问。<code>pub use</code> 字段就是解决这个问题的,让其他代码也能简单的导入代码。</p><p>这个的应用场景主要是,你为了自己更方便的管理自己的代码,所以分了很多层级结构,但其实其他代码并不是很在乎你这块代码的很多结构,就比如餐厅的例子,顾客不需要在乎你的前厅,你的后厨,顾客只在乎来餐厅吃饭的几个步骤,坐座位,点单,上菜,吃。所以你就可以使用 pub use,把这几个步骤从前厅后厨里导出来,方便顾客使用。</p><p>例子到后面跨文件的部分再举吧。</p><h2 id="嵌套路径use"><a href="#嵌套路径use" class="headerlink" title="嵌套路径use"></a>嵌套路径use</h2><p>当我们使用包内的多个模块的时候,每个都写一行 use 会占用很多地方,这时候就可以用嵌套路径的方法。用标准包为例子</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">use std::cmp::Ordering;</span><br><span class="line">use std::io;</span><br><span class="line"></span><br><span class="line">// 下面的写法和上面等价</span><br><span class="line">use std::{cmp::Ordering, io};</span><br></pre></td></tr></table></figure></div><p>也就是用花括号把同级的模块括起来一起导入,逗号分开。</p><p>如果我导入了一个模块,又想导入模块下面的一个函数呢?可以用 self 代表当前路径</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">use std::io;</span><br><span class="line">use std::io::Write;</span><br><span class="line"></span><br><span class="line">// 下面的写法和上面等价</span><br><span class="line">use std::{self, Write};</span><br></pre></td></tr></table></figure></div><p>如果我想导入一个模块的所有字段的?可以用统配符<code>*</code>,就相当于 <code>import *</code> 吧。是个坏文明,因为这样你就不知道你写的字段是不是和包内的字段重名了,别用。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">use std::io::*;</span><br></pre></td></tr></table></figure></div><h1 id="将模块拆分为不同文件"><a href="#将模块拆分为不同文件" class="headerlink" title="将模块拆分为不同文件"></a>将模块拆分为不同文件</h1><p>之前说过了模块的层级目录对吧,我们可以把这个层级目录转换为对应的文件层级目录,实现不同文件对应不同模块。</p><p>用之前写的代码 <code>front_of_house->hosting->add_to_waitlist</code> 为例子。</p><p>首先,我们需要在我们的库单元包声明一个前台模块。</p><h3 id="lib-rs"><a href="#lib-rs" class="headerlink" title="lib.rs"></a>lib.rs</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mod front_of_house;</span><br><span class="line"></span><br><span class="line">pub use crate::front_of_house::hosting;</span><br><span class="line"></span><br><span class="line">pub fn eat_at_restaurant() {</span><br><span class="line"> hosting::add_to_waitlist();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>然后,对应的再在 lib.rs 的同级目录下新建一个 front_of_house.rs 文件,对应模块的入口。声明 hosting 模块。</p><h3 id="front-of-house-rs"><a href="#front-of-house-rs" class="headerlink" title="front_of_house.rs"></a>front_of_house.rs</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pub mod hosting;</span><br></pre></td></tr></table></figure></div><p>然后,我们再在同级下新建一个文件夹,front_of_house,对应front_of_house模块下面的模块代码入口。再在 front_of_house 文件夹下新建一个 hosting.rs。添加进我们的 add_to_waitlist 代码</p><h3 id="front-of-house-hosting-rs"><a href="#front-of-house-hosting-rs" class="headerlink" title="front_of_house/hosting.rs"></a>front_of_house/hosting.rs</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pub fn add_to_waitlist(){</span><br><span class="line"> println!("add_to_waitlist");</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>修改完后,再 cargo run 一下,编译没有问题,依旧能够通过路径正常访问到 add_to_waitlist 函数~这就是模块层级路径和文件层级路径的对应关系,只需要声明模块后,给出一个模块的对应入口文件,就可以通过这种方式拆分为多个模块文件啦。</p><p>好,我们再来试试跨文件使用代码吧。</p><p>还记得我们一开始 cargo new 是 new 了一个名为 test 的包吧?lib.rs 作为库单元包,其实就是相当于 test 包的入口文件,用来声明 test 包下的单元包。</p><p>还记得我们之前使用了 pub use 把 hosting 引入到了 lib.rs 的作用域,也就是相当于引入到了 test 包的作用域。基于这个前置知识,我们就可以在 main.rs 里跨文件使用 add_to_waitlist 函数了。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">use test::hosting;</span><br><span class="line">fn main() {</span><br><span class="line"> println!("Hello, world!");</span><br><span class="line"> hosting::add_to_waitlist();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>运行成功输出 add_to_waitlist,也就是 pub use 在 test 包的作用域下声明了一个公有的字段 hosting,我们就可以通过路径,在 test 下访问到 hosting了。</p><p>当然,如果没有 pub use 我们也可以通过 <code>use test::front_of_house::hosting;</code> 的绝对路径去访问 hosting,但是注意,因为我们的 front_of_house 在 lib.rs 不是公有声明,所以 test 作为父级,是访问不到的,必须要声明为公有模块才可以访问(同理普通的 use 不能访问也是一个原理)。</p><hr><p>第七章结束,也是常规一章的知识,总结一下这章的内容就是</p><ol><li>模块的定义和使用方法</li><li>公有私有的权限管理,用结构体和枚举类型为例子更详细的说明了父子权限的关系</li><li>use 关键字的原理和用法</li><li>模块层级路径和文件层级路径可以相互转换管理</li></ol><p>今天就到这!</p>]]></content>
<summary type="html"><h1 id="包与单元包"><a href="#包与单元包" class="headerlink" title="包与单元包"></a>包与单元包</h1><ol>
<li>单元包(Crate):单元包可以被用来生成二进制的程序或者库;单元包的入口文件称为单元包的入口节点</l</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】6. 枚举与模式匹配</title>
<link href="https://twosix.page/2023/03/30/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%916-%E6%9E%9A%E4%B8%BE%E4%B8%8E%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D/"/>
<id>https://twosix.page/2023/03/30/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%916-%E6%9E%9A%E4%B8%BE%E4%B8%8E%E6%A8%A1%E5%BC%8F%E5%8C%B9%E9%85%8D/</id>
<published>2023-03-30T14:38:03.000Z</published>
<updated>2024-07-04T15:52:28.393Z</updated>
<content type="html"><![CDATA[<h1 id="定义枚举"><a href="#定义枚举" class="headerlink" title="定义枚举"></a>定义枚举</h1><p>例子:IP地址,只有 IPV4, IPV6 两个模式。</p><p>所以我们可以通过枚举类型的方式来描述 IP 地址的类型。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">IpAddKind</span>{</span><br><span class="line"> IPV4,</span><br><span class="line"> IPV6</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">four</span> = IpAddKind::IPV4;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">six</span> = IpAddKind::IPV6;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里也就两个点:</p><ol><li>使用 <code>enum</code> 关键字就可以完成一个枚举类型的定义。</li><li>访问枚举类型的值是通过命名空间的方式来实现的,而不是点运算符。</li></ol><p>接下来再谈论一个实际的问题:这里的枚举类型只定义了两个类别,没有办法对应实际的IP地址,怎么办?</p><p>一般情况下,我们首先想到的肯定是用结构体,一个字段存储类型,一个字段存储地址对吧。但是 Rust 有一个非常方便的特性:<strong>关联的数据可以直接嵌入到枚举类型里</strong>!</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">IpAddKind</span>{</span><br><span class="line"> <span class="title function_ invoke__">IPV4</span>(<span class="type">String</span>),</span><br><span class="line"> <span class="title function_ invoke__">IPV6</span>(<span class="type">String</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">local</span> = IpAddKind::<span class="title function_ invoke__">IPV4</span>(<span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">"127.0.0.1"</span>));</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">loopback</span> = IpAddKind::<span class="title function_ invoke__">IPV6</span>(<span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">"::1"</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>厉害吧。不过好像结构体也能做是吧?不完全是,枚举类型有一个结构体做不到的功能。</p><p>枚举类型可以为每个类型值指定一个类型,例如 IPV4 通常是四个数字来表示,而 IPV6 则不同,那么我们可以使用四个数字来描述 IPV4 ,使用字符串来描述 IPV6</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">IpAddKind</span>{</span><br><span class="line"> <span class="title function_ invoke__">IPV4</span>(<span class="type">u8</span>, <span class="type">u8</span>, <span class="type">u8</span>, <span class="type">u8</span>),</span><br><span class="line"> <span class="title function_ invoke__">IPV6</span>(<span class="type">String</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这个时候,把类型和内容拆分成两个字段来描述的结构体,就没有办法实现了,只能固定一种类型。</p><p>另外,IPV4和IPV6因为很常用,所以在标准库里其实就有定义好了一套枚举类型。它的定义方法是这样的。</p><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-03-30_21-21-30.jpg" alt="img" ></p><p>也就是先用两个结构体描述一下具体的 IPV4和IPV6 ,然后再定义一个枚举类型,把这两个结构体嵌入到枚举类型里。</p><p>另外,枚举类型还有对于结构体另外的优势是,<strong>我们可以轻松的定义一个用于处理多个数据类型的函数</strong>。</p><p>例如,我们可以像上面官方一样,用两个结构体去描述 IPV4和IPV6,但如果我们要定义一个函数,同时处理IP地址的话,就不知道该指定传入参数的类型是 IPV4还是IPV6了,但我们定义一个枚举类型 IpAddr ,就可以轻松的定义函数的传入类型为 IpAddr,然后可以同时处理两个结构体的数据。</p><h1 id="Option——一个常用的枚举类型"><a href="#Option——一个常用的枚举类型" class="headerlink" title="Option——一个常用的枚举类型"></a>Option——一个常用的枚举类型</h1><p>Option 枚举类型定义了值可能不存在的情况,或者可以说是其他语言的空值 <code>Null</code> 。本来就很常用了,但这个类型在 Rust 里更有用,<strong>因为编译器可以自动检查我们是不是妥善的处理了所有应该被处理的情况</strong>。</p><p>Rust 并没有像其他语言一样,有空值 <code>Null</code> 或者 <code>None</code> 的概念。书上说这是个错误的设计方法,因为当你定义了一个空值,在后续可能没有给他赋予其他值就进行使用的话,就会触发问题。这种设计理念可以帮助人们更方便的去实现一套系统,但也给系统埋下了更多的隐患。</p><p>Rust 结合了一下这个理念,觉得空值是有意义的,触发空值问题的本质是实现的措施的问题,所以提供了一个具有类似概念的枚举类型——Option. 标准库里的定义是这样的</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Option</span><T>{</span><br><span class="line"> <span class="title function_ invoke__">Some</span>(T),</span><br><span class="line"> <span class="literal">None</span>,</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>Option</code> 是被预导入的,因为它很有用,所以我们不用 use 来导入它。所以我们可以不用指定命名空间,使用 <code>Some</code> 或者 <code>None</code> 关键词, 是后面学的语法,用来代表任意类型</p><p>我们可以用这个方法来定义一些变量</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">some_number</span> = <span class="title function_ invoke__">Some</span>(<span class="number">5</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">some_string</span> = <span class="title function_ invoke__">Some</span>(<span class="string">"hello"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">absent_number</span> = <span class="literal">None</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段编译是不通过的,这也体现了 Option 相对于普通空值的优势。</p><p>我们来简单的通过几个例子来了解一下 Option 的设计理念</p><ol><li>Option和 T 是两个不同的类型</li></ol><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="title function_ invoke__">Some</span>(<span class="number">5</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span> = <span class="number">5</span>;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, a+b);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>可以看到这段代码里,虽然 a, b 同样都是 i32 但一个是被<code>Some</code>持有,那它们就是不同的类型,编译器无法理解不同类型相加,所以这就意味着什么。当我们对于一个可能有值,可能没值的变量,我们就要去使用 <code>Option</code> 枚举,一旦使用了<code>Option</code>枚举,我们再<strong>要实际使用它的时候,就要显式的把这个</strong> <code>Some</code> 类型的变量转换到 i32 的变量,再去使用。相当于强迫你去对这个变量编写一段代码,这样就避免了你不经过处理就使用,结果使用到空值的情况。</p><p>当然,因为是枚举类型,我们也可以方便的用 <code>match</code> 来处理不同的情况,例如有值时怎么样,没值时怎么样等等。接下来就开始介绍一下 <code>match</code></p><h1 id="match运算符"><a href="#match运算符" class="headerlink" title="match运算符"></a>match运算符</h1><p><code>match</code> 是一个很好用的运算符,它除了提高可读性外,也方便编译器对你的代码进行安全检查,确保你对所有可能的情况都进行了处理。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Coin</span>{</span><br><span class="line"> Penny,</span><br><span class="line"> Nickel,</span><br><span class="line"> Dime,</span><br><span class="line"> Quarter,</span><br><span class="line">}</span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">value_in_cents</span>(coin: Coin) <span class="punctuation">-></span> <span class="type">u8</span> {</span><br><span class="line"> <span class="keyword">match</span> coin {</span><br><span class="line"> Coin::Penny => <span class="number">1</span>,</span><br><span class="line"> Coin::Nickel => <span class="number">5</span>,</span><br><span class="line"> Coin::Dime => <span class="number">10</span>,</span><br><span class="line"> Coin::Quarter => <span class="number">25</span>,</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">my_coin</span> = Coin::Dime;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The value of my coin is {} cents"</span>, <span class="title function_ invoke__">value_in_cents</span>(my_coin));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这就是一个简单的,输入硬币类型,返回硬币价值的代码。相对于 <code>if-else</code> 表达式,match 的第一个优势自然就是可读性。你 if-else 需要想方设法凑一个 bool 值,可能用字符串,可能用字典,可能用数字下标,可能用结构体什么的,但 match 枚举类型就不用,可以为任意你想要的类型定义一个名字,直接用,直接返回任意值,结构还更紧凑好看。</p><p>另一个优势就是,match 运算符可以匹配枚举变量的部分值。</p><p>美国的25美分硬币里,很多个州都有不同的设计,也只有25美分的硬币有这个特点,所以我们可以给25美分加一个值:州,对应不同的设计</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[derive(Debug)]</span></span><br><span class="line"><span class="keyword">enum</span> <span class="title class_">UsState</span>{</span><br><span class="line"> Alabama,</span><br><span class="line"> Alaska,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">enum</span> <span class="title class_">Coin</span>{</span><br><span class="line"> Penny,</span><br><span class="line"> Nickel,</span><br><span class="line"> Dime,</span><br><span class="line"> <span class="title function_ invoke__">Quarter</span>(UsState),</span><br><span class="line">}</span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">value_in_cents</span>(coin: Coin) <span class="punctuation">-></span> <span class="type">u8</span> {</span><br><span class="line"> <span class="keyword">match</span> coin {</span><br><span class="line"> Coin::Penny => <span class="number">1</span>,</span><br><span class="line"> Coin::Nickel => <span class="number">5</span>,</span><br><span class="line"> Coin::Dime => <span class="number">10</span>,</span><br><span class="line"> Coin::<span class="title function_ invoke__">Quarter</span>(state) => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The state of coin is {:?}"</span>, state);</span><br><span class="line"> <span class="number">25</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">my_coin</span> = Coin::<span class="title function_ invoke__">Quarter</span>(UsState::Alaska);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The value of my coin is {} cents"</span>, <span class="title function_ invoke__">value_in_cents</span>(my_coin));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里我们用到了之前的 Debug 注解,让编译器自动格式化枚举类型的输出,然后在match匹配里,提取了 <code>Quarter</code> 枚举类型里的值,命名为 <code>state</code>,然后输出。</p><h2 id="匹配Option"><a href="#匹配Option" class="headerlink" title="匹配Option"></a>匹配Option</h2><p>接下来我们就可以综合一下上面学到的东西,用match来处理一下空和非空的情况了。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">pluse_one</span>(x: <span class="type">Option</span><<span class="type">i32</span>>) <span class="punctuation">-></span> <span class="type">Option</span><<span class="type">i32</span>> {</span><br><span class="line"> <span class="keyword">match</span> x {</span><br><span class="line"> <span class="literal">None</span> => <span class="literal">None</span>,</span><br><span class="line"> <span class="title function_ invoke__">Some</span>(i) => <span class="title function_ invoke__">Some</span>(i + <span class="number">1</span>),</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num</span> = <span class="title function_ invoke__">Some</span>(<span class="number">5</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num2</span> = <span class="title function_ invoke__">pluse_one</span>(<span class="literal">None</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这就是一个典型的例子:</p><ol><li>当为空的时候,什么也不干</li><li>当不为空的时候,取出值,进行处理</li></ol><p>这也就是 match 的相对于 if-else 的优势:可以以一个更简单,更紧凑,可读性更高的方式,进行模式匹配,进行对应值的处理。接下来就介绍 match 在安全性方面的优势:让编译器帮你检查是否处理完了所有情况。</p><h2 id="必须穷举所有可能"><a href="#必须穷举所有可能" class="headerlink" title="必须穷举所有可能"></a>必须穷举所有可能</h2><p>我们来试着漏处理为空值的情况。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">pluse_one</span>(x: <span class="type">Option</span><<span class="type">i32</span>>) <span class="punctuation">-></span> <span class="type">Option</span><<span class="type">i32</span>> {</span><br><span class="line"> <span class="keyword">match</span> x {</span><br><span class="line"> <span class="title function_ invoke__">Some</span>(i) => <span class="title function_ invoke__">Some</span>(i + <span class="number">1</span>),</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num</span> = <span class="title function_ invoke__">Some</span>(<span class="number">5</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>编译器马上报错:<code>non-exhaustive patterns: None not covered</code> 你没有覆盖处理空值!</p><p>这一段代码同时体现了 match 的优势和 Option 实现空值的优势。也就是你必须要处理所有情况,保证没有一点逻辑遗漏的地方,Option也依赖于 match 的这个特性,强迫你处理空值的情况,杜绝了大部分程序员只写一部分 if-else 结果就因为漏了部分情况没处理,导致程序 crash 的问题。</p><h2 id="通配符"><a href="#通配符" class="headerlink" title="_ 通配符"></a>_ 通配符</h2><p>但有时候我明明不用处理所有情况,但每次都要写上很麻烦怎么办?没事,Rust 作为一个现代语言还是准备了一些语法糖,那就是通配符 <code>_</code> 相当于 if-else 里面的 else。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">pluse_one</span>(x: <span class="type">Option</span><<span class="type">i32</span>>) <span class="punctuation">-></span> <span class="type">Option</span><<span class="type">i32</span>> {</span><br><span class="line"> <span class="keyword">match</span> x {</span><br><span class="line"> <span class="title function_ invoke__">Some</span>(i) => <span class="title function_ invoke__">Some</span>(i + <span class="number">1</span>),</span><br><span class="line"> _ => <span class="literal">None</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num</span> = <span class="title function_ invoke__">Some</span>(<span class="number">5</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>_</code> 可以匹配所有类型,所以得放最后,用于处理前面没有被处理过的情况。</p><p>但有时候我指向对一种情况处理怎么办?还是有点麻烦吧,Rust 还准备了 <code>if let</code> 语句</p><h1 id="简单控制流-if-let"><a href="#简单控制流-if-let" class="headerlink" title="简单控制流 if-let"></a>简单控制流 if-let</h1><p>我们就继续用美分硬币来举例吧,之前写的代码方便些。例如我们只想知道这个硬币是不是 Penny。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Coin</span>{</span><br><span class="line"> Penny,</span><br><span class="line"> Nickel,</span><br><span class="line"> Dime,</span><br><span class="line"> Quarter,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">my_coin</span> = Coin::Penny;</span><br><span class="line"> <span class="keyword">match</span> my_coin{</span><br><span class="line"> Coin::Penny => <span class="built_in">println!</span>(<span class="string">"Lucky Penny!"</span>),</span><br><span class="line"> _ => <span class="built_in">println!</span>(<span class="string">"Not a penny"</span>),</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>用 match 的话,就是要定义一个硬币,匹配这个硬币,再写个通配符来检测其他情况,标准流程是吧。但这样写或许繁琐了些,if let 就是这么一个一步到位的语法</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Coin</span>{</span><br><span class="line"> Penny,</span><br><span class="line"> Nickel,</span><br><span class="line"> Dime,</span><br><span class="line"> Quarter,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">my_coin</span> = Coin::Penny;</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Coin</span>::Penny = my_coin {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"Lucky penny!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>if let Coin::Penny</code> 就是要匹配的值,<code>my_coin</code>也就是你传入的值,用等号分隔开,如果两者相等的话,执行花括号内的语句。</p><p>当然,也可以用<code>else</code></p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Coin</span>{</span><br><span class="line"> Penny,</span><br><span class="line"> Nickel,</span><br><span class="line"> Dime,</span><br><span class="line"> Quarter,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">my_coin</span> = Coin::Penny;</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Coin</span>::Penny = my_coin {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"Lucky penny!"</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"Not a lucky penny!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这样就和上面的 match 完全匹配了。</p><p>虽然 if let 让代码写起来更简单了,但也失去了 match 的安全检查,所以是一个取舍吧。个人偏向于使用match,说实话我觉得这 if let 写起来有点别扭,感觉书写逻辑不太舒服。</p><p>这应该只是一个偶尔可能用到的糖,哪时候你写烦了match,可以想想原来还有 If let 这么个东西</p><hr><p>第六章也就搞定了,大致总结一下,这章主要就是讲了一下1. 枚举类型对于结构体的优势所在,2. match对于if-else的优势所在,3. 独特的空值设计理念</p><p>今天就到这里吧,后面还有7,8,9章三章的基础通用编程知识,学完就差不多进入进阶阶段了。</p>]]></content>
<summary type="html"><h1 id="定义枚举"><a href="#定义枚举" class="headerlink" title="定义枚举"></a>定义枚举</h1><p>例子:IP地址,只有 IPV4, IPV6 两个模式。</p>
<p>所以我们可以通过枚举类型的方式来描述 IP 地址的类型</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust 学习记录】5. 结构体</title>
<link href="https://twosix.page/2023/03/30/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%915-%E7%BB%93%E6%9E%84%E4%BD%93/"/>
<id>https://twosix.page/2023/03/30/%E3%80%90Rust-%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%915-%E7%BB%93%E6%9E%84%E4%BD%93/</id>
<published>2023-03-30T13:05:00.000Z</published>
<updated>2024-07-04T15:52:28.393Z</updated>
<content type="html"><![CDATA[<h1 id="定义及实例化方式"><a href="#定义及实例化方式" class="headerlink" title="定义及实例化方式"></a>定义及实例化方式</h1><h2 id="定义和创建实例"><a href="#定义和创建实例" class="headerlink" title="定义和创建实例"></a>定义和创建实例</h2><p>定义方法和 C++ 是一模一样了,详见代码</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">struct User{</span><br><span class="line"> username: String,</span><br><span class="line"> email: String,</span><br><span class="line"> sign_in_count: u64,</span><br><span class="line"> active: bool,</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>实例化方法稍显不同,方法和定义差不多,指名道姓的赋值,优势是不用对应顺序,可读性强。访问方法就还是传统的点运算符</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">struct User{</span><br><span class="line"> username: String,</span><br><span class="line"> email: String,</span><br><span class="line"> sign_in_count: u64,</span><br><span class="line"> active: bool,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let user1 = User{</span><br><span class="line"> email: String::from("[email protected]"),</span><br><span class="line"> username: String::from("haha"),</span><br><span class="line"> sign_in_count: 1,</span><br><span class="line"> active: true,</span><br><span class="line"> };</span><br><span class="line"> println!("user1: {}", user1.email);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>同理,结构体也有可变与不可变一说,<strong>可变结构体,结构体所有变量可变,不可变结构体,结构体所有变量不可变,Rust 没有让结构体部分变量可变,部分不可变的说法</strong></p><h2 id="一些语法糖"><a href="#一些语法糖" class="headerlink" title="一些语法糖"></a>一些语法糖</h2><h3 id="同名参数对应赋值"><a href="#同名参数对应赋值" class="headerlink" title="同名参数对应赋值"></a>同名参数对应赋值</h3><p>如果每次都要写一个 email: xxxx, username: xxxx 好像有点麻烦是吗?Rust 提供了一个简单的方法,<strong>当变量名和结构体内的字段名完全一样的时候,会对应赋值</strong>(有一说一,这个设计挺有想法的,语法糖+1)</p><p>所以我们可以很轻松的给 User 写一个创建用的函数,这样就可以实现带默认值,轻松的构建结构体实例了</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">struct User{</span><br><span class="line"> username: String,</span><br><span class="line"> email: String,</span><br><span class="line"> sign_in_count: u64,</span><br><span class="line"> active: bool,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn create_user(email: String, username: String) -> User {</span><br><span class="line"> User {</span><br><span class="line"> email,</span><br><span class="line"> username,</span><br><span class="line"> active: true,</span><br><span class="line"> sign_in_count: 1,</span><br><span class="line"> }</span><br><span class="line"> // 不写分号返回 User 变量</span><br><span class="line">}</span><br><span class="line">fn main() {</span><br><span class="line"> let user1 = create_user(String::from("[email protected]"), String::from("haha"));</span><br><span class="line"> println!("user1: {}", user1.email);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="用之前的实例构造现在的实例"><a href="#用之前的实例构造现在的实例" class="headerlink" title="用之前的实例构造现在的实例"></a>用之前的实例构造现在的实例</h3><p>在一些情况下其实结构体内的实例都不需要怎么变动,就像上面的例子里,<code>sign_in_count</code> 和 <code>active</code> 参数都是采用同一个默认值来赋给所有实例的,那有没有一些方法能简化这种情况的代码书写呢?有的。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let user1 = create_user(String::from("[email protected]"), String::from("haha"));</span><br><span class="line"> let user2 = User {</span><br><span class="line"> email: String::from("[email protected]"),</span><br><span class="line"> username: String::from("user2"),</span><br><span class="line"> ..user1</span><br><span class="line"> };</span><br><span class="line"> println!("user2: {}", user2.active);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>..user1</code> 就代表了剩下的值都和 <code>user1</code> 一样,把 <code>user1</code> 里对应字段的值赋给 <code>user2</code> 即可。(这个感觉不如函数封装性好吧,但可能也看情况)</p><h2 id="元组结构体——没有字段名的结构体"><a href="#元组结构体——没有字段名的结构体" class="headerlink" title="元组结构体——没有字段名的结构体"></a>元组结构体——没有字段名的结构体</h2><p>其实就相当于给元组命个名字,适用于很多不需要给字段命名的情况下,例如颜色的RGB,大家都懂是吧,就用一个三元组命名 <code>Color</code> 就好了。定义方式如下</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">struct Color(i8, i8, i8);</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let black = Color(0, 0, 0);</span><br><span class="line"> println!("user2: {}", black.0);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>值得一提的是,定义成结构体后也是用点运算符访问变量,而不是 [] 运算符了</p><h2 id="空结构体——没有字段的结构体"><a href="#空结构体——没有字段的结构体" class="headerlink" title="空结构体——没有字段的结构体"></a>空结构体——没有字段的结构体</h2><p>当你想创建一个空结构体的时候,也是不会报错的,原理说是和空元组相似,然后在某些方面会有用,后续再介绍。</p><p>我也不太懂,就不多说了,后面再看吧。</p><h1 id="结构体的所有权"><a href="#结构体的所有权" class="headerlink" title="结构体的所有权"></a>结构体的所有权</h1><p>在上面举的这个例子中</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">struct User{</span><br><span class="line"> username: String,</span><br><span class="line"> email: String,</span><br><span class="line"> sign_in_count: u64,</span><br><span class="line"> active: bool,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let user1 = User{</span><br><span class="line"> email: String::from("[email protected]"),</span><br><span class="line"> username: String::from("haha"),</span><br><span class="line"> sign_in_count: 1,</span><br><span class="line"> active: true,</span><br><span class="line"> };</span><br><span class="line"> println!("user1: {}", user1.email);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>我们定义的所有字段都是具有值的所有权的,所以结构体实例能具有所有字段数据的所有权,能伴随着自己直到离开作用域,但也有不具有所有权的定义方式</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">struct User{</span><br><span class="line"> username: &str,</span><br><span class="line"> email: &str,</span><br><span class="line"> sign_in_count: u64,</span><br><span class="line"> active: bool,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let user1 = User{</span><br><span class="line"> email: String::from("[email protected]"),</span><br><span class="line"> username: String::from("haha"),</span><br><span class="line"> sign_in_count: 1,</span><br><span class="line"> active: true,</span><br><span class="line"> };</span><br><span class="line"> println!("user1: {}", user1.email);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>但这种方式现在是没办法定义通过的,报错提示需要指定生命周期,这个涉及到了生命周期,所以就放到后面介绍了。</p><h1 id="初试trait——为结构体增加更多有用的功能"><a href="#初试trait——为结构体增加更多有用的功能" class="headerlink" title="初试trait——为结构体增加更多有用的功能"></a>初试trait——为结构体增加更多有用的功能</h1><p>说实话我不知道这个trait是什么意思,大概查了一下,是 特性(性状)的意思,用来定义一个类型可能和其他类型共享的功能,或许差不多相当于和 python 里的 <strong>xxx</strong> 差不多吗?但看样子还能自己定义的样子。先不管吧,大概了解一下概念,先学着。</p><h2 id="打印结构体"><a href="#打印结构体" class="headerlink" title="打印结构体"></a>打印结构体</h2><p>用过 python 的应该都知道 python 类有个 <code>__str__</code> 函数,可以定义一个类的字符串格式,方便输出成人类能查看的格式,而不是一串地址,Rust 的结构体也有这个功能—— <code>Display</code>。</p><p>我们可以先跑一下这段代码</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">struct rectangle{</span><br><span class="line"> w: u32,</span><br><span class="line"> h: u32</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let rect = rectangle{w: 10, h: 20};</span><br><span class="line"> println!("rectangle is {}", rect);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>可以看到一个报错</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">= help: the trait `std::fmt::Display` is not implemented for `rectangle`</span><br><span class="line">= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead</span><br></pre></td></tr></table></figure></div><p><code>the trait std::fmt::Display is not implemented for rectangle </code>意思就是这个结构体还没实现 <code>Display</code> 这个方法,也就是说,<code>println!</code> 这个宏在输出的时候,还会调用一下类型的格式化函数,来进行指定的输出,之前我们用的基本类型都是默认实现了 <code>Display</code> 方法的,而这个 <code>rectangle</code> 是我们自己定义的,没有 <code>Display</code> 方法,<code>println!</code> 就不知道怎么格式化了,所以就报错。</p><p>但除了这个还有一个有意思的提示 <code>in format strings you may be able to use {:?} (or {:#?} for pretty-print) instead</code> 。这句话告诉我们,可以用 <code>{:?}{:#?}</code> 来整一个漂亮的输出?啥意思?写一下吧~</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">= help: the trait `Debug` is not implemented for `rectangle`</span><br><span class="line">= note: add `#[derive(Debug)]` to `rectangle` or manually `impl Debug for rectangle`</span><br></pre></td></tr></table></figure></div><p>被骗了,还是报错。但我们发现了另一个有意思的 trait—— <code>Debug</code> 。也就是说,Rust 对格式化的方法区分了两种,Debug 是专门面向开发者调试时的输出用格式。我超,什么是现代化语法啊(后仰)。这种特性真能派上不少用途。</p><p>提示里说,要么添加<code> [derive(Debug)]</code> 要么自己实现一个 <code>Debug</code> 我们可以先添加一下这个试试</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#[derive(Debug)]</span><br><span class="line">struct rectangle{</span><br><span class="line"> w: u32,</span><br><span class="line"> h: u32</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let rect = rectangle{w: 10, h: 20};</span><br><span class="line"> println!("rectangle is {:?}", rect);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>添加在函数头,进行一个 <code>Debug</code> 注解就可以了,这次运行就不会报错了。我们来看看输出是怎样的</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rectangle is rectangle { w: 10, h: 20 }</span><br></pre></td></tr></table></figure></div><p>可见 Rust 标准的 <code>Debug</code> 格式化输出就是 结构体名{所有字段名: 对应的值},嗯,挺不错的,不用自己手动一个一个输出了。我们再来看看之前提示里提到的另一个 <code>{:#?}</code> 吧</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">rectangle is rectangle {</span><br><span class="line"> w: 10,</span><br><span class="line"> h: 20,</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这个输出会更好看点,有了一定的排版,对于复杂的结构体会更有可读性。</p><p>好,书上的 trait 介绍就到这里结束了(是不是把一开始的 <code>Format</code> 忘了),它说到第 10 章的时候会更详细的介绍 trait 的时候,可以通过像这种对结构体进行 trait 注解的方式提供很多功能,包括自带的,甚至可以自己自定义,确实期待。</p><h1 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h1><p>结构体,或者说类,当然不能少了函数,书上对普通函数和结构体里的函数区分了一下概念,结构体内的函数叫方法,因为方法的定义有局限性,例如参数要有self,只能定义在结构体或trait之类的地方,属于是一个子集吧。我们也严谨点,区分一下吧。</p><h2 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">#[derive(Debug)]</span><br><span class="line">struct Rectangle{</span><br><span class="line"> w: u32,</span><br><span class="line"> h: u32</span><br><span class="line">}</span><br><span class="line">impl Rectangle {</span><br><span class="line"> fn area(&self) -> u32 {</span><br><span class="line"> self.w * self.h</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let rect = Rectangle{w: 10, h: 20};</span><br><span class="line"> println!("rectangle's area is {:#?}", rect.area());</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>(吐槽:这里把 <code>rectangle</code> 的首字母大写了,因为 Rust 的编译器居然会警告我的命名不规范,牛)</p><p>可以看见,方法和函数定义差不多,也是用 <code>fn</code> 来定义,指定哪个函数里的话倒是比较意外,居然不是写在 <code>Rectangle </code>定义的花括号里,而是另开一个 <code>impl Rectangle</code> 再来定义方法。另外,Rust 方法和 Python 也有点相似之处,也是通过 <code>self</code> 来指代当前实例,<code>self</code> 可以用三种方式来定义</p><ol><li><code>&self</code> :不可变引用,这个是最常见的,我们只要读取数据,什么也不干,所以不需要用到所有权,也最方便</li><li><code>self</code>:获取所有权,应该最不常见,有时方法需要用来转换<code>self</code>类型的话,需要用到所有权,获取所有权后再进行返回;如果不返回的话,所有权在调用完方法就被回收了,实例就销毁了。</li><li><code>&mul self</code>:可变引用,也没什么好说的,就是有时要改变实例内字段的值时会用。</li></ol><p>然后书上介绍了 Rust 为什么没有 <code>-></code> 运算符的问题,我没太看懂,就简述一下我的理解,具体感兴趣可以自行看书或查阅资料。</p><p>C++在对于一个指针类型的结构体变量里,需要对变量进行一次解引用,也就是 <code>ractangle->area()= (*rectangle).area()</code> 所以额外定义了一个 <code>-></code> 运算符写起来方便些。而 Rust 里,self的类型被显式定义了,所以编译器可以自动的根据你定义的 <code>self</code> 类型,去自动推理出 <code>self</code> 是否需要自动引用还是解引用,所以就不需要 <code>-></code> 运算符了。</p><h2 id="关联函数"><a href="#关联函数" class="headerlink" title="关联函数"></a>关联函数</h2><p>在 <code>impl</code> 块里,除了带 <code>self</code> 的方法之外,Rust 还允许在块里定义不含 <code>self</code> 的函数,这些函数因为和结构体有关联,又不太需要 <code>self</code> 所以称为关联函数。和 Python 的 <code>@staticmethod</code> 差不多吧</p><p>这里用一个定义函数来举例</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">#[derive(Debug)]</span><br><span class="line">struct Rectangle{</span><br><span class="line"> w: u32,</span><br><span class="line"> h: u32</span><br><span class="line">}</span><br><span class="line">impl Rectangle {</span><br><span class="line"> fn area(self) -> u32 {</span><br><span class="line"> self.w * self.h</span><br><span class="line"> }</span><br><span class="line"> fn square(size: u32) -> Rectangle {</span><br><span class="line"> Rectangle { w: size, h: size }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let rect = Rectangle{w: 10, h: 20};</span><br><span class="line"> println!("rectangle's area is {:#?}", rect.area());</span><br><span class="line"> println!("rectangle is {:#?}", Rectangle::square(3));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>使用也就是指定命名空间调用就可以了。</p><h2 id="多个impl块"><a href="#多个impl块" class="headerlink" title="多个impl块"></a>多个impl块</h2><p>使用多个 impl 块也是合法的,可以编译通过。书上说后面会有应用场景介绍,那就后面再看吧,目前感觉还派不上用场?</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">#[derive(Debug)]</span><br><span class="line">struct Rectangle{</span><br><span class="line"> w: u32,</span><br><span class="line"> h: u32</span><br><span class="line">}</span><br><span class="line">impl Rectangle {</span><br><span class="line"> fn area(self) -> u32 {</span><br><span class="line"> self.w * self.h</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">impl Rectangle {</span><br><span class="line"> fn square(size: u32) -> Rectangle {</span><br><span class="line"> Rectangle{w: size, h: size}</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">fn main() {</span><br><span class="line"> let rect = Rectangle{w: 10, h: 20};</span><br><span class="line"> println!("rectangle's area is {:#?}", rect.area());</span><br><span class="line"> println!("rectangle is {:#?}", Rectangle::square(3));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><hr><p>本章到此结束!没什么好总结的,是比较基础的,也很重要的一部分,下一章继续干。</p>]]></content>
<summary type="html"><h1 id="定义及实例化方式"><a href="#定义及实例化方式" class="headerlink" title="定义及实例化方式"></a>定义及实例化方式</h1><h2 id="定义和创建实例"><a href="#定义和创建实例" class="header</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust学习记录】4. 所有权</title>
<link href="https://twosix.page/2023/03/26/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%914-%E6%89%80%E6%9C%89%E6%9D%83/"/>
<id>https://twosix.page/2023/03/26/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%914-%E6%89%80%E6%9C%89%E6%9D%83/</id>
<published>2023-03-26T12:18:45.000Z</published>
<updated>2024-07-04T15:52:28.396Z</updated>
<content type="html"><![CDATA[<p>所有权和生命周期据说是Rust最难学也最核心的两个概念,也是Rust在没有垃圾回收的机制下确保内存安全的秘诀,现在就能开始接触这第一咯核心概念了。</p><h1 id="什么是所有权"><a href="#什么是所有权" class="headerlink" title="什么是所有权"></a>什么是所有权</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>一般内存管理就两种:1. 自动垃圾回收:在运行的时候定期检查并回收没有使用的内存;2. 程序员手动分配和释放;Rust提出了第三种规则,这套规则<strong>目的在于能让编译器在编译的过程中就检查内存问题</strong>,不需要在运行的时候花费代价去回收垃圾。</p><hr><p>补充概念:</p><ul><li>栈:后进先出的内存分配结构,没有办法在中间插入存放数据,所以存放在栈里的数据需要已知且固定大小</li><li>堆:堆的管理比较松散,你可以在堆里请求一个特定的大小的空间,操作系统就会找到一片足够大的地方,标记为已使用,分配给你,返回你一个指向这片地方的指针,因为是指针,所以也方便再申请一块地方,然后把这两块地方串起来,实现动态大小。但因为多了指针跳转,也要不断的寻找足够大的空间,所以在堆里存取数据会比栈里慢。</li></ul><p>一般语言都不需要深入了解这两个概念,但书上说这两个概念和Rust的所有权紧密相关,所以我们暂且先看看。</p><hr><h2 id="所有权规则"><a href="#所有权规则" class="headerlink" title="所有权规则"></a>所有权规则</h2><p>暂时了解,后续会逐一解释</p><ol><li>每一个<strong>值</strong>都有一个对应的<strong>变量</strong>,作为值的<strong>所有者</strong></li><li>在同一时间内,值<strong>有且仅有</strong>一个所有者</li><li>当所有者<strong>离开了自己的作用域</strong>,它持有的值就会被<strong>释放</strong>掉</li></ol><h2 id="变量作用域"><a href="#变量作用域" class="headerlink" title="变量作用域"></a>变量作用域</h2><p>这个和其他语言是一模一样的,不费口舌了。</p><p>简单来说就是变量只在作用域里变的有效,保持有效直到离开作用域</p><h2 id="String类型——一个例子"><a href="#String类型——一个例子" class="headerlink" title="String类型——一个例子"></a>String类型——一个例子</h2><p>String是一个存储在堆上的结构,用这个举例会能更好的说明所有权的作用,这一部分主要<strong>注重于所有权的部分,而不是去了解关注String</strong></p><h3 id="简单例子"><a href="#简单例子" class="headerlink" title="简单例子"></a>简单例子</h3><p>我们定义一个动态可变长的字符串</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut s = String::from("hello world");</span><br><span class="line"> s.push_str("!!");</span><br><span class="line"> println!("{}", s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>对于一个可变长度的String变量而言,内存管理主要分两个步骤</p><ol><li>让操作系统给 String 分配一个堆空间</li><li>使用完之后,把内存交还给操作系统</li></ol><p>第一步在大多数语言里都是一样的,那就是让程序员去发起请求,也就是定义一个变量。</p><p>第二步就不一样了,也就是上面介绍过的,要么定期检查自动回收,要么程序员自己来完成。定期回收吧,开销太大,自己完成吧,实现起来又很困难,一不小心回收晚了——内存泄漏,回收早了——非法变量,重复回收了,也可能有无法预知的后果。</p><p>所以 Rust 的解决方案是,在变量离开作用域后,立即释放内存。(其实我看到这里还觉得很普通啊,这不是很正常的操作吗?内存泄漏一般是不小心哪里弄了点跨文件的全局变量,一直被 hold 着不释放导致的吧)</p><p>Rust 回收是通过一个叫 drop 的函数进行的,也就是说,在 main 函数执行完后,其实 Rust 在花括号后面偷偷调用了一次 drop 函数。</p><p>但是,看一下复杂的例子,就能发现一点不一样的地方了</p><h3 id="复杂例子"><a href="#复杂例子" class="headerlink" title="复杂例子"></a>复杂例子</h3><p>让我们试着定义两个存放在栈里的变量,两个存放在堆里的String变量</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let x = 5;</span><br><span class="line"> let y = x;</span><br><span class="line"> let s1 = String::from("hello world");</span><br><span class="line"> let s2 = s1;</span><br><span class="line"> println!("{}", s1);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>存放在栈里的变量,很符合正常逻辑,我们会创建一个值5给x,然后把 x 里的值拷贝一份,再给 y ,这样我们就有两个5了,互相修改互不影响。</p><p>但堆里的不一样,为了保证效率(之前也说了在堆里存取很浪费效率),Rust 在创建堆变量的时候,会附带一个指针,指向这个堆,就像这样</p><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-03-26_18-05-12.jpg" alt="img" ></p><p>这说明什么,说明我们创建 s2 的时候其实只是拷贝了 s1 的内容,没有拷贝值的内容,只是拷贝了一份新的字段,以及一个新的指针,指向原来的内存块,所以修改的时候,是会相互影响的。好,这一部分也很好理解,毕竟不少语言也是这么干的。</p><p>但是!<strong>重点来了</strong>,之前说过,当重复释放一片内存的时候,可能会造成不可预计的错误,那我们 s2 和 s1 不就在同一个作用域吗,按 Rust 的所有权方法,在离开的时候不就同时释放了这块内存吗?</p><p>于是,Rust 用了一个很简单粗暴的方法,解决了这个问题。那就是,<strong>当两个变量同时指向了同一块内存的时候,上一个变量就没用了</strong>!(此处印证了第一条规则,值有且仅有一个所有者)</p><p>你可以尝试一下运行之前的代码,编译器是会报错的,也就是说,在定义了 s2 之后,无法输出 s1 Rust以此来保证没有一块内存是冗余的。奇葩!</p><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-03-26_18-13-35-1024x175.jpg" alt="img" ></p><p>报错提示你,你真的要用两个变量名的话,就给编译器说明,你确定是浪费内存,去要克隆一份。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s1 = String::from("hello world");</span><br><span class="line"> let s2 = s1.clone();</span><br><span class="line"> println!("s1:{}, s2:{}", s1, s2);</span><br><span class="line"> // 这样就合法了</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>但也正如上面所说,栈是不受影响的,因为固定长度的变量在栈里操作很快,复制一份也无所谓,所以 x,y 是可以随便用的。</p><h2 id="所有权与函数"><a href="#所有权与函数" class="headerlink" title="所有权与函数"></a>所有权与函数</h2><p>基于上面的例子,我们就可以发现 Rust 这套规则的作用域,和别的语言完全不同的地方。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s1 = String::from("hello");</span><br><span class="line"> string_test(s1);</span><br><span class="line"> let x:i32 = 5;</span><br><span class="line"> i_test(x);</span><br><span class="line"> println!("{}", x);</span><br><span class="line"> println!("{}", s1);</span><br><span class="line">}</span><br><span class="line">fn string_test(s: String) {</span><br><span class="line"> println!("{}", s);</span><br><span class="line">}</span><br><span class="line">fn i_test(i: i32){</span><br><span class="line"> println!("{}", i);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>我们可以看一下这段代码,当 s1 被传入函数 string_test 的时候,其实也相当于完成了一次复制,也就是说,把 s1 的值复制给了函数参数 s,导致两者也指向了同一片空间。这代表什么?这段代码编译肯定不会通过,因为 <strong>s1 的作用域到执行函数 string_test 就已经结束了</strong>!而 x 不会受这个影响。</p><p>果然还是大受震撼,让人不禁产生疑问,那要让人怎么随心所欲调用函数了?</p><p>同时,函数的返回值也受这个所有权影响,也就是说,当执行返回值的时候,返回值的所有权回到函数上,再交由函数赋予变量上。</p><p>针对我提出的疑问,书上马上也给出了回答,如果要让我在调用函数之后保证变量的所有权,那就需要在函数的最后加个返回值,再把所有权返回给我的变量,也就是所用权的变更路线是 s1——参数——返回值——函数——s1</p><p>这也太麻烦了,这时候就需要引入另一个概念,让这个操作变得没那么繁琐,那就是——引用。</p><h1 id="引用和借用"><a href="#引用和借用" class="headerlink" title="引用和借用"></a>引用和借用</h1><h2 id="引用和所有权"><a href="#引用和所有权" class="headerlink" title="引用和所有权"></a>引用和所有权</h2><p>既然复制和移动会转移所有权,导致变量有效性消失的问题,那不复制不移动不就完了?这个操作,就需要用到引用。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s1 = String::from("hello");</span><br><span class="line"> string_test(&s1);</span><br><span class="line"> println!("{}", s1);</span><br><span class="line">}</span><br><span class="line">fn string_test(s: &String) {</span><br><span class="line"> println!("{}", s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码就完全没有问题了。</p><p>引用和别的语言概念也一样,应该不需要多说,创建一个新的引用,它的本质是这样的。</p><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-03-26_18-57-53.jpg" alt="img" ></p><p>也就是说,什么也不拷贝,但是多了个新的指针,指向原变量,值的所有权还是在s1上,并且<strong>引用不会持有所有权</strong>,所以当 s 离开了作用域,它的值也不会被回收。</p><p>在 Rust 里,这种通过引用传递给函数参数的方法,称为借用,也就是你用完了别人的东西,要原封不动的还给人家。没错,<strong>原封不动</strong>,这又是和其他语言不太一样的地方。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut s1 = String::from("hello");</span><br><span class="line"> string_test(&s1);</span><br><span class="line"> println!("{}", s1);</span><br><span class="line">}</span><br><span class="line">fn string_test(s: &String) {</span><br><span class="line"> s.push_str(" world!");</span><br><span class="line"> println!("{}", s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>让我们把 s1 修改为可变,然后通过引用传给 s ,试图修改一下值,不出意外,编译器报错!引用是不可变的。</p><p>但其实很多时候,我们确实需要在函数里修改变量,怎么办?</p><p>所以又有了可变引用</p><h2 id="可变引用"><a href="#可变引用" class="headerlink" title="可变引用"></a>可变引用</h2><h3 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut s1 = String::from("hello");</span><br><span class="line"> string_test(&mut s1);</span><br><span class="line"> println!("{}", s1);</span><br><span class="line">}</span><br><span class="line">fn string_test(s: &mut String) {</span><br><span class="line"> s.push_str(" world!");</span><br><span class="line"> println!("{}", s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>&mut</code> 就是可变引用的关键字,这里我们把参数定义为了可变引入,传入的时候也改成了可变引用,代码就合法了。但 Rust 怎么可能让你这么自由的写代码?这不安全!所以可变引用有非常大的限制。那就是<strong>可变引用只能一次声明一个</strong>!</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut s1 = String::from("hello");</span><br><span class="line"> let s2 = &mut s1;</span><br><span class="line"> let s3 = &mut s1;</span><br><span class="line"> println!("{}, {}", s2, s3);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>不出意外,这段代码必然报错。</p><h3 id="可变引用与数据竞争"><a href="#可变引用与数据竞争" class="headerlink" title="可变引用与数据竞争"></a>可变引用与数据竞争</h3><p>这么做的主要原因是让我们在编译的时候<strong>避免数据竞争</strong>,当指令在满足以下三种情况的时候,就会有数据竞争的情况:</p><ol><li>两个或两个以上的指针同时访问一片空间</li><li>其中至少有一个,要往空间写入数据</li><li>而且又没有同步数据访问的机制</li></ol><p>看了以上三种情况应该也能大概直到数据竞争是什么了,大概就是,写和读同步进行,可能导致另一个指针读到的数据不太对,导致你完全无法察觉的bug。</p><p>这种情况在 Rust 完全不会出现。因为可能产生数据竞争的代码编译这一关就通过不了哈哈哈。(同理,以上代码如果你不使用 s2, s3 的话其实不会报错,因为你定义了两个,但都没有使用,自然也没有数据竞争,只会有警告,告诉你定义了两个没用的变量)</p><p>基于这个理由,我们也可以知道,同时存在不可变引用+可变引用也是不合法的,因为一个只读,一个可能写,也会有数据竞争。而同时存在多个不可变引用的话,就没问题,因为它们都是只读,并不会修改数据。</p><h2 id="悬垂引用"><a href="#悬垂引用" class="headerlink" title="悬垂引用"></a>悬垂引用</h2><p>在别的语言里,有一个概念叫 <strong>悬垂指针</strong>,也就是说,一个指针指着块内存,但是内存被释放掉了,指针还指着这块内存,就叫悬垂。在 Rust 里,同样有一套规则确保引用不会进入悬垂状态,具体做法就是,确保引用的内存不会在引用离开自己的作用域时就被释放掉。也就是说,<strong>编译器保证引用在作用域内持续有效</strong></p><p>先来创建一个悬垂引用</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let test = dangle();</span><br><span class="line">}</span><br><span class="line">fn dangle() -> &String {</span><br><span class="line"> let s = String::from("hello");</span><br><span class="line"> &s</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这里面我们返回了一个引用,但是引用的数据是 s 里的,s 在离开了函数后就会被销毁,引用自然也就悬垂了。</p><p>这时候编译会报错:<code>expected named lifetime parameter</code></p><p>报错涉及到了我们之前说的两大最难学的核心概念之一,生命周期,这个会在后面学,现在不管。我们只要直到,Rust 又一次成功的通过报错拦截了我们的危险代码。</p><p>所以我们需要及时的去规范我们的代码,避免危险,这里也很简单,我们不返回引用,而是创建一个字符串变量,返回它的所有权就行了。</p><p>到这里,引用就讲完了,展开下一个概念,切片。(小声逼逼,这章好长,一边看书,一边写代码,一边写博客,看了我一下午了,没办法还是想一章一章的完整看完)</p><h1 id="切片"><a href="#切片" class="headerlink" title="切片"></a>切片</h1><p>之前说过,引用没有所有权,但没有所有权的类型还有一个,那就是——切片。用过 python 的应该很清楚这个概念。</p><p>切片在 Rust 的<strong>本质就是引用几何里一段连续的元素序列</strong>。</p><p>书里举了一个例子说明切片的好处。</p><p>假设我们需要获取一个句子里面某个单词,怎么获取?最简单的方法的方法就是找到第一个单词的索引,知道单词的长度,这样就能随时的通过下标的方式访问到单词。</p><p>但这种设计方式有一个问题,那就是单词的索引,它的意义是和单词严格绑定的,当我的句子都已经被销毁的时候,其实索引也就没有了意义,但这时候我们可能用了一个变量来存这个索引,这个变量又不随着句子而销毁,这样就造成了一些冗余的问题,就连 Rust 的编译器也没办法给你挑出毛病来(你也有今天)。</p><p>所以就有了切片,我们一次性切出来一块引用,引用这个单词相关的所有字符,当原来的句子没有用了之后,引用也自然会被销毁(没被销毁的情况编译器就报错了)</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s = String::from("hello world");</span><br><span class="line"> let hello = &s[0..5];</span><br><span class="line"> let world = &s[6..11];</span><br><span class="line"> println!("{} {}", hello, world);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>使用方法也很简单,在 python 里是冒号,这里就是两个点,也同样是左闭右开,但是注意要写引用。</p><p>语法糖也和 python 一样,如果你想从一开始就切,也可以不写第一个数字,如果你想切到最后,也可以不写最后一个数字,例如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let s = String::from("hello world");</span><br><span class="line"> let hello = &s[..5];</span><br><span class="line"> let world = &s[6..];</span><br><span class="line"> println!("{} {}", hello, world);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>另外,有趣的是,之前不是说编译器会保证引用持续有效吗?那我在引用离开作用域前手动销毁会怎样?</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut s = String::from("hello world");</span><br><span class="line"> let hello = &s[..5];</span><br><span class="line"> let world = &s[6..];</span><br><span class="line"> s.clear();</span><br><span class="line"> println!("{} {}", hello, world);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>当然,肯定会报错。但它的解决方法比较有趣,之前说过,当你定义了不可变引用的时候,就没办法定义可变引用了对吧。而clear本质也是个函数,它清空 s 的内存的话,本质上是需要修改 s 的内容,所以它需要传入一个 s 的可变引用,来对齐进行清空,但我们之前还定义了不可变引用,不可变引用还没进行使用呢,你就没有办法定义可变引用去clear了。没错,根本上还是解决数据竞争的问题,清空本质上是一个写操作,我还要读呢,你就不能写!</p><p>当然,同样的,如果你不用读,它就不会报错了,例如:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fn main() {</span><br><span class="line"> let mut s = String::from("hello world");</span><br><span class="line"> let hello = &s[..5];</span><br><span class="line"> let world = &s[6..];</span><br><span class="line"> s.clear();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>书上还提到了其他类型,例如数组也可以切片,此乃废话,不多说。</p><hr><p>好勒,第4章到这里就终于结束了,这一章实在太长了,毕竟涉及到核心概念,看了我半天时间,今天就差不多到这吧。</p>]]></content>
<summary type="html"><p>所有权和生命周期据说是Rust最难学也最核心的两个概念,也是Rust在没有垃圾回收的机制下确保内存安全的秘诀,现在就能开始接触这第一咯核心概念了。</p>
<h1 id="什么是所有权"><a href="#什么是所有权" class="headerlink" title=</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust学习记录】3. 通用编程概念</title>
<link href="https://twosix.page/2023/03/25/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%913-%E9%80%9A%E7%94%A8%E7%BC%96%E7%A8%8B%E6%A6%82%E5%BF%B5/"/>
<id>https://twosix.page/2023/03/25/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%913-%E9%80%9A%E7%94%A8%E7%BC%96%E7%A8%8B%E6%A6%82%E5%BF%B5/</id>
<published>2023-03-25T11:48:36.000Z</published>
<updated>2024-07-04T15:52:28.396Z</updated>
<content type="html"><![CDATA[<h1 id="变量,可变性,隐藏,常量"><a href="#变量,可变性,隐藏,常量" class="headerlink" title="变量,可变性,隐藏,常量"></a>变量,可变性,隐藏,常量</h1><p>这部分的内容基本都在前面了解过了,大致就是一下内容</p><h3 id="变量与可变性"><a href="#变量与可变性" class="headerlink" title="变量与可变性"></a>变量与可变性</h3><p>Rust 默认变量是不可变的,需要可变的话需要加上 <code>mut</code> 关键字。</p><h3 id="隐藏"><a href="#隐藏" class="headerlink" title="隐藏"></a>隐藏</h3><p>但没有 <code>mut</code> 的变量也可以进行修改,那就是 shadow 机制(翻译为“隐藏”,其实我不太能接受,因为“隐藏”这个词汇常常涉及到安全隐患,但在 Rust 中并非如此)。我们可以通过再 <code>let</code> 一个同名变量,来修改。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">x</span> = <span class="number">4</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">x</span> = <span class="number">5</span>;</span><br></pre></td></tr></table></figure></div><p>这是可以编译通过的。</p><p>通过 shadow 来修改变量,和定义 <code>mut</code> 来修改变量的区别是:shadow 可以修改变量的类型,就和我们猜数游戏的 <code>guess</code> 一样,一开始存字符串,后面存 i32,但不同类型的变量在相互赋值时,是会报错的。</p><h3 id="常量"><a href="#常量" class="headerlink" title="常量"></a>常量</h3><p>和变量不同的是,常量需要显式声明类型,并且必须用常量表达式来赋值。常量使用 <code>const</code> 关键字声明,而不是 <code>let</code>。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">const</span> MAX_NUM:<span class="type">u32</span> = <span class="number">100_000</span>;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The value of MAX_NUM is: {}"</span>, MAX_NUM);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>值得一提的是,Rust 支持在数字中间加个下划线提高可读性</p><h1 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h1><p>Rust 本质还是一个静态语言,需要显示的给出变量的具体类型。不过有不少情况,编译器能根据实际情况推导出我们的实际类型罢了,但像 <code>guess</code> 需要作类型转换的时候,还是需要给出具体的类型,上面的常量定义的时候,也需要显示给出变量类型。</p><p>定义变量类型的方式也就是上面那样,用冒号加类型</p><p>如果要加的时候没有加类型,就会报 <code>cannot infer type for xxxx</code> 意思就是编译器推导不出来类型了,要你给。</p><h2 id="标量类型"><a href="#标量类型" class="headerlink" title="标量类型"></a>标量类型</h2><p>基本的类型,整型,浮点型,布尔类型,字符型</p><h3 id="整型"><a href="#整型" class="headerlink" title="整型"></a>整型</h3><p>根据长度命名,有符号为 <code>i8</code>, <code>i16</code>, <code>i32</code>, <code>i64</code>,无符号为 <code>u8</code>, <code>u16</code>, <code>u32</code>, <code>u64</code></p><p>比较特殊的是<code> isize</code>, <code>usize</code>,这两个的长度根据本地环境而定,如果运行的环境是32位系统就是32位,64位系统就是64位。感觉挺牛的。</p><h3 id="浮点型"><a href="#浮点型" class="headerlink" title="浮点型"></a>浮点型</h3><p>只有两种,<code>f32</code>和<code>f64</code>,值得一提的时候,Rust 默认会将浮点型推导为 <code>f64</code>,表述方法是 IEEE-754</p><h3 id="布尔型"><a href="#布尔型" class="headerlink" title="布尔型"></a>布尔型</h3><p>没什么好说的,<code>:bool</code></p><h3 id="字符型"><a href="#字符型" class="headerlink" title="字符型"></a>字符型</h3><p>Rust 用的字符型是用 Unicode 的,而不是 ASCLL,占4个字节;定义和C++一样,字符是单引号,字符串是双引号。</p><p>但 Unicode 实际并没有“字符”的概念,所以有点奇怪,书上说是后面会解释</p><h3 id="复合类型"><a href="#复合类型" class="headerlink" title="复合类型"></a>复合类型</h3><p>基础提供的有 <code>tuple</code> 和 <code>array</code></p><p>定义方面和 python 像,<code>tuple</code> 用圆括号定义,<code>array</code> 用方括号定义。</p><p>底层方面和 c++ 差不多,array 在栈上分配一整片内存,而 tupple 是在堆上分配不一定连续的内存</p><p>但两者都是长度不可变的,如果要数组长度可变,可以使用动态数组 <code>vector</code>,这里书上没介绍</p><p>tuple 的基本操作:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">a</span>:(<span class="type">i32</span>, <span class="type">u32</span>, <span class="type">f32</span>) = (<span class="number">1</span>, <span class="number">2</span>, <span class="number">3.0</span>); <span class="comment">// 指定类型</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">b</span> = (<span class="number">1</span>, <span class="number">2</span>, <span class="number">3.0</span>); <span class="comment">// 不指定类型,默认推导浮点为 f64</span></span><br><span class="line"><span class="keyword">let</span> (x, y, z) = b; <span class="comment">// 把 b 拆出来,复制给 x, y, z 三个变量</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">c</span> = b.<span class="number">0</span> <span class="comment">//通过下标访问元组</span></span><br></pre></td></tr></table></figure></div><p>值得一提的是,这里的模式匹配赋值需要带括号 let (x, y, z) = b;</p><p>array 的基本操作:</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">a</span>:[<span class="type">i32</span>; <span class="number">5</span>] = [<span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>] <span class="comment">// 指定类型</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">b</span> = [<span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>] <span class="comment">//不指定类型</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">c</span> = [<span class="number">3</span>; <span class="number">5</span>] <span class="comment">// 用; 重复定义5个3</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">first</span> = a[<span class="number">0</span>] <span class="comment">// 通过下标访问</span></span><br></pre></td></tr></table></figure></div><p>Rust 在每次执行数组下标访问的时候,都会作边界检查,如果访问越界的话会抛出异常,然后终止程序运行。而不像 c++ 会自作主张的运行,造成某些难以察觉的 bug</p><h1 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h1><p>函数怎么定义的,在前面也讲过了,就是 <code>fn</code>,其中传入参数的类型,返回值的类型必须显示定义。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 指定数据类型,返回值类型</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">plus_one</span>(x:<span class="type">i32</span>) <span class="punctuation">-></span> <span class="type">i32</span>{</span><br><span class="line"> x + <span class="number">1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="语句和表达式"><a href="#语句和表达式" class="headerlink" title="语句和表达式"></a>语句和表达式</h2><p>在Rust里有两个基本的概念,</p><ol><li>语句:执行操作,但不返回值</li><li>表达式:进行计算,返回计算值</li></ol><p><code>let</code> 操作就是一个语句,所以不能进行</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">a</span> = (<span class="keyword">let</span> <span class="variable">b</span> = <span class="number">1</span>);</span><br></pre></td></tr></table></figure></div><p>当然也不能写 <code>a = b = 1</code> 这样的语句,因为这些在Rust都是语句,不返回值,不能赋值</p><p>而 <code>let b = 1</code> 中的 1 ,本身就是表达式,返回1, 函数也是个表达式,返回函数的返回值,而我们写的花括号<code>{}</code>,本质上也是个表达式,也能返回值,所以上面的语句我们可以写成</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">a</span> = {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">b</span> = <span class="number">1</span>;</span><br><span class="line"> b + <span class="number">1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>花括号就是一个表达式吗,它执行一系列的操作,并返回一个返回值,<code>b+1</code> 也是一个表达式,返回 <code>b+1</code> 的值,<strong>值得一提的是,</strong><code>b + 1</code>后面没加分号,如果加了分号,那它就是语句,不是表达式,不会返回值了,这时候编译也会报错。这应该也是之前看到的,Rust 返回值的办法,默认不需要用 <code>return</code> 语句,而是采用表达式的办法。</p><p><strong>Rust 默认函数的返回值就是最后一个表达式,但可以使用 return 关键字提前返回</strong></p><p>如果函数没有返回值,函数会返回一个空元组;没有指定返回类型,也会默认类型是空元组</p><p>若类型不匹配,自然就会编译报错</p><h1 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h1><p>没什么好说的 //</p><h1 id="控制流"><a href="#控制流" class="headerlink" title="控制流"></a>控制流</h1><h2 id="if-else表达式"><a href="#if-else表达式" class="headerlink" title="if-else表达式"></a>if-else表达式</h2><p>语法和 c++ 几乎一样,但不需要打圆括号,好评</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num</span> = <span class="number">3</span>;</span><br><span class="line"> <span class="keyword">if</span> num<<span class="number">4</span> && num < <span class="number">5</span> {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"num is less than 4 and 5"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> num > <span class="number">7</span>{</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"num is greater than 7"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"num is greater than 4 and less than 7"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>值得一提的是,rust 里的 <code>if</code> 表达式只接受 <code>bool</code> 值,而不会像其他一些语言一样,把不等于 0 的值自动当成 <code>bool</code> 值。也就是在上例不能写成 <code>if num{};</code></p><p>同样,这里<code>if else</code>是<strong>表达式</strong>,而不是语句,也就是说,它可以返回值,所以我们也能用 if 表达式给变量赋值。但同时需要注意类型问题,两个分支不同类型的赋值,还是会编译报错</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">condition</span> = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num</span> = <span class="keyword">if</span> condition { <span class="number">5</span> } <span class="keyword">else</span> { <span class="number">6</span> };</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The value of num is: {}"</span>, num);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h2 id="循环"><a href="#循环" class="headerlink" title="循环"></a>循环</h2><h3 id="loop循环"><a href="#loop循环" class="headerlink" title="loop循环"></a>loop循环</h3><p>前面已经介绍过 <code>loop </code>循环表达式了,就是相当于 <code>while true</code>,值得一提的是,表达式,对,<code>loop</code> 也是一个表达式,可以通过 <code>break</code> 返回值。没错,<strong>在 Rust 里 break 居然可以返回值</strong>!</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = <span class="keyword">loop</span>{</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">num</span> = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">break</span> num*<span class="number">5</span>;</span><br><span class="line"> };</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The value of a is: {}"</span>, a);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="while-循环"><a href="#while-循环" class="headerlink" title="while 循环"></a>while 循环</h3><p>很常规的循环,但<code> while</code> 就不能用来返回值了,<code>break</code> 不能带数值。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">num</span> = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> num<=<span class="number">5</span> {</span><br><span class="line"> num += <span class="number">1</span>;</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"The value of num is: {}"</span>, num);</span><br><span class="line"> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><h3 id="for-循环"><a href="#for-循环" class="headerlink" title="for 循环"></a>for 循环</h3><p>Rust 里的 <code>for</code> 循环和python差不多,是通过迭代器来进行循环的,这在遍历像数组一样的结构时比较舒服,主要是1. 不会有越界的危险 2. 不需要每次执行过后作一次条件判断。</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>];</span><br><span class="line"> <span class="comment">// 普通for循环,和python很像</span></span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> a.<span class="title function_ invoke__">iter</span>() {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, i);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 需要获取循环次数的时候,也和python一样,使用enumerate</span></span><br><span class="line"> <span class="keyword">for</span> (idx, val) <span class="keyword">in</span> a.<span class="title function_ invoke__">iter</span>().<span class="title function_ invoke__">enumerate</span>() {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"idx:{}, val:{}"</span>, idx, val);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 循环一个范围的数的时候</span></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>];</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> <span class="number">0</span>..<span class="number">4</span>{</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, a[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 也可以逆序遍历</span></span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> (<span class="number">0</span>..<span class="number">4</span>).<span class="title function_ invoke__">rev</span>(){</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"{}"</span>, a[i]);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p><code>0..4</code> 确实有点惊到我,抽象程度还挺高。</p><hr><p>至此,第三章基本概念结束!这章里面,虽然基本知识很多,但也有不少Rust独特之处,挺惊喜的,可以看到很多地方有些和 python 的相似程度,在保证和 c++ 差不多效率的情况下,在一些语法简洁方面向python看齐(例如自动推导类型),处处都体现着,这确实是一个很现代化的语言。</p>]]></content>
<summary type="html"><h1 id="变量,可变性,隐藏,常量"><a href="#变量,可变性,隐藏,常量" class="headerlink" title="变量,可变性,隐藏,常量"></a>变量,可变性,隐藏,常量</h1><p>这部分的内容基本都在前面了解过了,大致就是一下内容</p>
</summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust学习记录】2. 猜数游戏——尝试代码编写</title>
<link href="https://twosix.page/2023/03/25/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%912-%E7%8C%9C%E6%95%B0%E6%B8%B8%E6%88%8F%E2%80%94%E2%80%94%E5%B0%9D%E8%AF%95%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99/"/>
<id>https://twosix.page/2023/03/25/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%912-%E7%8C%9C%E6%95%B0%E6%B8%B8%E6%88%8F%E2%80%94%E2%80%94%E5%B0%9D%E8%AF%95%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99/</id>
<published>2023-03-25T06:45:41.000Z</published>
<updated>2024-07-04T15:52:28.396Z</updated>
<content type="html"><![CDATA[<h1 id="输入与输出的尝试"><a href="#输入与输出的尝试" class="headerlink" title="输入与输出的尝试"></a>输入与输出的尝试</h1><h2 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h2><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cargo new guess_game</span><br><span class="line"><span class="built_in">cd</span> .\guess_game\</span><br><span class="line">cargo run</span><br></pre></td></tr></table></figure></div><p>就是之前介绍的,用 cargo 创建项目的步骤,run 成功了就表明没问题啦</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::io;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>(){</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"欢迎来玩猜数游戏!"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"输入一个数字:"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">guess</span> = <span class="type">String</span>::<span class="title function_ invoke__">new</span>();</span><br><span class="line"> io::<span class="title function_ invoke__">stdin</span>().<span class="title function_ invoke__">read_line</span>(&<span class="keyword">mut</span> guess).<span class="title function_ invoke__">expect</span>(<span class="string">"读取输入失败!"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"你输入的数字是:{}"</span>, guess);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ul><li><code>use</code>:就是 Rust 里的导入语句,这里的意思是导入 <code>std</code> 库里的 <code>io</code> 模块。</li><li><code>let</code>:就是 Rust 里的定义语句,<code>let a = b;</code> 就是定义一个新的变量 <code>a</code>,值为 <code>b</code>。但值得一提的是,和其他所有程序都不一样,Rust 里直接定义的变量都是 <code>const</code> 常量,不可变!需要用 <code>mut</code> 关键词声明这个变量是可变的变量。</li><li><code>string::new()</code>:没什么好说的,<code>new</code> 了一个 <code>String</code> 类型,是空白的字符串。</li><li><code>io::stdin().read_line(&mut guess)</code>:也就是调用 <code>io</code> 模块里的 <code>stdin</code> 实例的 <code>read_line</code> 函数,如果没有写 <code>use</code>,也可以用 <code>std::io::stdin</code> 表示,<code>&</code> 和 C++ 里一样,是引用的概念,也就是说,定义了一个新的引用,和上面的 <code>guess</code> 指向同一个地址,用来接输入。</li><li><code>.expect</code> 就是异常处理语句,在执行完 <code>read_line</code> 后,会返回一个 <code>Result</code> 类型的值,通常是一个枚举类型,<code>Ok</code> 和 <code>Err</code> 两个值,<code>Ok</code> 就是执行成功,并且附带代码产生的结果值,这里就是输入的字节数;<code>Err</code> 就是执行错误,附带错误原因。用 <code>expect</code> 就可以很方便地处理异常,而不用再写各 if-else。</li><li>值得一提的是,不写 <code>expect</code> 的话,虽然能编译通过,但 Rust 会提示你有个地方没处理异常,就像这样:</li></ul><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-03-24_23-15-36.jpg" alt="img" ></p><p>(听说有人不喜欢写异常处理是吧)</p><p>最后一句,就是标准的格式化花括号占位输出,没什么好说的。</p><h1 id="引入第三方包的尝试"><a href="#引入第三方包的尝试" class="headerlink" title="引入第三方包的尝试"></a>引入第三方包的尝试</h1><h2 id="声明依赖"><a href="#声明依赖" class="headerlink" title="声明依赖"></a>声明依赖</h2><p>一般语言的标准库都有生成随机数的函数,但rust没有。所以我们需要引入rust官方提供的一个随机数包——rand</p><p>打开Cargo.toml文件,在dependencies后面添加rand包。</p><div class="code-container" data-rel="Toml"><figure class="iseeu highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[package]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">"guess_game"</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">"0.1.0"</span></span><br><span class="line"><span class="attr">edition</span> = <span class="string">"2021"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html</span></span><br><span class="line"></span><br><span class="line"><span class="section">[dependencies]</span></span><br><span class="line"><span class="attr">rand</span> = <span class="string">"0.3.14"</span></span><br></pre></td></tr></table></figure></div><p>0.3.14版本就是版本号。</p><p>然后再build一次项目,cargo就会自动帮你搜索包以及对应的并下载了,包的信息一般是从 crates.io 获取</p><h2 id="cargo-lock和cargo-update"><a href="#cargo-lock和cargo-update" class="headerlink" title="cargo.lock和cargo update"></a>cargo.lock和cargo update</h2><p>cargo引入了一个独特的机制来保证依赖的版本问题,让所有人在构建这个项目的时候都得到相同的结果。你第一次构建项目的时候,cargo就会遍历我们声明的依赖以及版本号,把它写到 lock 文件里,后面再构建的时候,就会都使用这个版本的依赖了,除非手动升级到其他版本。</p><p>如果实在想升级,使用 <code>cargo update</code> 命令就会无视 lock 强行升级依赖</p><h1 id="使用match进行分支控制的尝试"><a href="#使用match进行分支控制的尝试" class="headerlink" title="使用match进行分支控制的尝试"></a>使用match进行分支控制的尝试</h1><p>rust提供了match语法来进行更简洁的分支控制。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">use std::io;</span><br><span class="line">use std::cmp::Ordering;</span><br><span class="line">use rand::Rng;</span><br><span class="line">fn main(){</span><br><span class="line"> println!("欢迎来玩猜数游戏!");</span><br><span class="line"> let secret_number = rand::thread_rng().gen_range(1, 101);</span><br><span class="line"> println!("正确答案是:{}", secret_number);</span><br><span class="line"> println!("输入一个数字:");</span><br><span class="line"> let mut guess = String::new();</span><br><span class="line"> io::stdin().read_line(&mut guess)</span><br><span class="line"> .expect("读取输入失败!");</span><br><span class="line"> let guess: i32 = guess.trim().parse()</span><br><span class="line"> .expect("请输入正确的数字!");</span><br><span class="line"> match guess.cmp(&secret_number){</span><br><span class="line"> Ordering::Less => {</span><br><span class="line"> println!("猜小啦");</span><br><span class="line"> println!("再猜一次:");</span><br><span class="line"> },</span><br><span class="line"> Ordering::Greater => println!("猜大啦"),</span><br><span class="line"> Ordering::Equal => println!("猜对啦"),</span><br><span class="line"> Other => println!("猜错啦"),</span><br><span class="line"> }</span><br><span class="line"> println!("你输入的数字是:{}", guess);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>这段代码里:</p><ul><li><code>use</code> 了一个 <code>Ordering</code> 的模块,这个模块提供了顺序对应的枚举类型,也就是 <code>Less</code>, <code>Greater</code>, <code>Equal</code></li><li><code>trim().parse()</code> 语句用来作类型转换,因为 <code>gen_range</code> 生成的 <code>secret_number</code> 是 <code>i32</code> 类型,无法直接与输入的字符串进行比较,所以作了一个类型转换;其中 <code>trim()</code> 就是去掉首尾多余的字符,空格换行什么的;<code>parse()</code> 是用于将字符串解析为对应数值类型的方法,同样也会抛出 <code>Result</code> 可以用于异常处理</li><li><code>match guess.cmp</code>:就是通过 <code>match</code> 语法进行分支控制,把 <code>guess.cmp()</code> 的结果丢到下面去匹配,匹配到什么就执行什么的语句。其中 <code>cmp</code> 返回的就是 <code>Ordering</code> 这个枚举类型。实际中,这个枚举类型也可以自己定义,方便自己的分支控制。传统的 <code>if-else</code> 也能实现这个逻辑就是。</li></ul><p>PS:大概了解了一下 <code>if-else</code> 和 <code>match</code> 的比较,两者应该主要是在可读性上的区别比较明显。</p><p>在可读性上,<code>if-else</code> 只接受 bool 值的二类判断。当有复杂条件的时候,就需要多层嵌套 <code>if-else</code> 比较难看。相对的,<code>match</code> 可以自定义枚举类型,在多类判断的时候,写法也更简洁,可读性更佳。当然,二类判断当属 <code>if-else</code>。</p><p>在性能上,当匹配的模式非常多的情况下,<code>match</code> 可以在编译时就完成判断,而 <code>if-else</code> 是在运行的时候完成判断,在极端的情况下,<code>match</code> 的性能会更佳(但我觉得在这种语句上纠性能的意义并不大)。</p><p>另外了解了一下,<code>if-else</code> 的语法和 C++ 基本一样,就不另外写了,书上目前也没有介绍 <code>if-else</code>。</p><h1 id="循环的尝试"><a href="#循环的尝试" class="headerlink" title="循环的尝试"></a>循环的尝试</h1><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::io;</span><br><span class="line"><span class="keyword">use</span> std::cmp::Ordering;</span><br><span class="line"><span class="keyword">use</span> rand::Rng;</span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>(){</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"欢迎来玩猜数游戏!"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">secret_number</span> = rand::<span class="title function_ invoke__">thread_rng</span>().<span class="title function_ invoke__">gen_range</span>(<span class="number">1</span>, <span class="number">101</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"正确答案是:{}"</span>, secret_number);</span><br><span class="line"> <span class="keyword">loop</span>{</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"输入一个数字:"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">guess</span> = <span class="type">String</span>::<span class="title function_ invoke__">new</span>();</span><br><span class="line"> io::<span class="title function_ invoke__">stdin</span>().<span class="title function_ invoke__">read_line</span>(&<span class="keyword">mut</span> guess)</span><br><span class="line"> .<span class="title function_ invoke__">expect</span>(<span class="string">"读取输入失败!"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"你输入的数字是:{}"</span>, guess);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">guess</span>: <span class="type">i32</span> = guess.<span class="title function_ invoke__">trim</span>().<span class="title function_ invoke__">parse</span>()</span><br><span class="line"> .<span class="title function_ invoke__">expect</span>(<span class="string">"请输入正确的数字!"</span>);</span><br><span class="line"> <span class="keyword">match</span> guess.<span class="title function_ invoke__">cmp</span>(&secret_number){</span><br><span class="line"> Ordering::Less => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"猜小啦"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"再猜一次:"</span>);</span><br><span class="line"> },</span><br><span class="line"> Ordering::Greater => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"猜大啦"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"再猜一次:"</span>);</span><br><span class="line"> },</span><br><span class="line"> Ordering::Equal => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"猜对啦"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>想使用一个 <code>while True</code> 循环也很简单,在 Rust 里就是一个 <code>loop</code> 语法,外带 <code>break</code>,没什么多说的,这里注意定义 <code>guess</code> 变量要在 <code>loop</code> 里,不然 <code>read_line</code> 会不断的在 <code>guess</code> 后面添加字符,就会导致无法转换成数字,报错退出。</p><p>那么在循环里有一个问题就很明显了,那就是 <code>expect</code> 语句不是我所理解常规的 <code>try-catch</code> 异常处理语句,它并没有捕获+后处理的步骤,所以它是会导致程序报错退出的。</p><p>那怎么来进行异常处理呢?之前也说过,<code>Result</code> 返回的是一个枚举类型 <code>Ok</code> 和 <code>Err</code>。那不就是,用 <code>match</code> 来处理就完了嘛?~</p><div class="code-container" data-rel="Rust"><figure class="iseeu highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::io;</span><br><span class="line"><span class="keyword">use</span> std::cmp::Ordering;</span><br><span class="line"><span class="keyword">use</span> rand::Rng;</span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>(){</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"欢迎来玩猜数游戏!"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">secret_number</span> = rand::<span class="title function_ invoke__">thread_rng</span>().<span class="title function_ invoke__">gen_range</span>(<span class="number">1</span>, <span class="number">101</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"正确答案是:{}"</span>, secret_number);</span><br><span class="line"> <span class="keyword">loop</span>{</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"输入一个数字:"</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">guess</span> = <span class="type">String</span>::<span class="title function_ invoke__">new</span>();</span><br><span class="line"> io::<span class="title function_ invoke__">stdin</span>().<span class="title function_ invoke__">read_line</span>(&<span class="keyword">mut</span> guess)</span><br><span class="line"> .<span class="title function_ invoke__">expect</span>(<span class="string">"读取输入失败!"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"你输入的数字是:{}"</span>, guess);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">guess</span>: <span class="type">i32</span> = <span class="keyword">match</span> guess.<span class="title function_ invoke__">trim</span>().<span class="title function_ invoke__">parse</span>(){</span><br><span class="line"> <span class="title function_ invoke__">Ok</span>(num) => num,</span><br><span class="line"> <span class="title function_ invoke__">Err</span>(_) => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"请输入一个正确的数字!"</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">match</span> guess.<span class="title function_ invoke__">cmp</span>(&secret_number){</span><br><span class="line"> Ordering::Less => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"猜小啦"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"再猜一次:"</span>);</span><br><span class="line"> },</span><br><span class="line"> Ordering::Greater => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"猜大啦"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"再猜一次:"</span>);</span><br><span class="line"> },</span><br><span class="line"> Ordering::Equal => {</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"猜对啦"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><p>修改后的代码是这样的,我们用 <code>match</code> 来处理 <code>parse()</code> 的返回值,之前也说过,<code>Ok</code> 会附带执行成功的值,所以 <code>Ok(num)</code> 表示,用 <code>num</code> 来匹配 <code>Ok</code> 里面带的成功的返回值,<code>=></code> 表示返回 <code>num</code> 值;<code>Err(_)</code> 就是表示,用 <code>_</code> 来匹配错误信息,因为不需要用,所以用 <code>_</code> 就完了,然后执行错误处理,输出+继续输入;值得一提的是这里好像说明了 Rust 的函数返回机制,好像不需要写 <code>return</code>,直接一个变量名就是返回了。</p><p>OK,到目前为止,初步的编程尝试已经结束了,看了下后面的章节介绍,应该会是更结构化的东西。</p>]]></content>
<summary type="html"><h1 id="输入与输出的尝试"><a href="#输入与输出的尝试" class="headerlink" title="输入与输出的尝试"></a>输入与输出的尝试</h1><h2 id="创建项目"><a href="#创建项目" class="headerlink" </summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
<entry>
<title>【Rust学习记录】1. 开发环境搭建</title>
<link href="https://twosix.page/2023/03/24/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%911-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
<id>https://twosix.page/2023/03/24/%E3%80%90Rust%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E3%80%911-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</id>
<published>2023-03-24T14:14:11.000Z</published>
<updated>2024-07-04T15:52:28.395Z</updated>
<content type="html"><![CDATA[ <div class="note p-4 mb-4 rounded-small info"> <p>本系列参考书目:《RUST权威指南》</p> </div><p>久闻rust大名,趁着研究生还是学习生涯,抽空出来试试这个所谓的既高效,又安全的语言</p><h1 id="环境安装与搭建"><a href="#环境安装与搭建" class="headerlink" title="环境安装与搭建"></a>环境安装与搭建</h1><h2 id="Rust安装"><a href="#Rust安装" class="headerlink" title="Rust安装"></a>Rust安装</h2><p>windows的安装很傻瓜式,只要进入<a class="link" href="https://www.rust-lang.org/learn/get-started" >官网<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>,下载最新版本的安装包,按照进行安装即可。期间可能会提示让你安装VS的工具,照着安装即可。</p><p>安装完成后,就可以通过 <code>rustc --version</code> 来测试是否安装成功了。</p><p>同时,安装后rust也会在本地生成一份文档,可以通过 <code>rustup doc</code> 用浏览器打开。</p><h1 id="Hello-World"><a href="#Hello-World" class="headerlink" title="Hello World!"></a>Hello World!</h1><p>接下来进行一个开启一门全新语言的必备仪式,hello world!</p><ol><li>新建一个文件命名为 <code>hello.rs</code> rs就是rust代码文件的后缀</li><li>编写代码</li></ol><div class="code-container" data-rel="Cpp"><figure class="iseeu highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">fn <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> println!(<span class="string">"Hello, world!"</span>); </span><br><span class="line">}</span><br></pre></td></tr></table></figure></div><ol start="3"><li>编译程序 <code>rustc hello.rs</code> ,接下来就会看到文件夹内生成了一个可执行文件 hello.exe,执行它,就能看到你的 hello world啦~跨过这一步,我们就是一名rust开发者了</li></ol><p>稍微了解一下这个函数</p><p>首先fn就是function的缩写,用来定义函数;其他语法都和c++差不多,唯独不一样的就是函数println后带着一个感叹号,这似乎是rust里的宏机制,这个后面再学学吧。</p><h1 id="Cargo的安装与使用"><a href="#Cargo的安装与使用" class="headerlink" title="Cargo的安装与使用"></a>Cargo的安装与使用</h1><h2 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h2><p>书上写的是,cargo是构建+包管理的工具,或许可以理解成,ubuntu里的apt+cmake的组合?</p><p>在安装rust时就已经同步安装了cargo,我们可以通过 <code>cargo --version</code> 来检查是否正确安装</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo new hello_cargo</span><br></pre></td></tr></table></figure></div><p>这一个命令会使用cargo来创建新的项目架构,我们进入创建的hello_cargo文件夹,可以看到cargo帮我们初始化了一个Cargo.toml文件,一个src目录,放置了一个main.rs的源文件,还有一个.gitignore文件,说明它还帮我们配置了git版本管理</p><p>关于toml文件,我们可以用编辑器打开它</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[package]</span><br><span class="line">name = "hello_cargo"</span><br><span class="line">version = "0.1.0"</span><br><span class="line">edition = "2021"</span><br><span class="line"></span><br><span class="line"># See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html</span><br><span class="line"></span><br><span class="line">[dependencies]</span><br></pre></td></tr></table></figure></div><p>我们可以看到这么些东西,上面自然就是你所创建的代码包的相关信息,而dependencies就是我们代码需要依赖的第三方包信息,不过我们一个hello world不需要什么,所以现在这里是空的。</p><p>也就是说,这是个rs项目的标准配置文件</p><p>我们再打开main.rs,就会发现cargo已经帮我们写好了一个hello world程序~</p><h2 id="编译、运行与发布"><a href="#编译、运行与发布" class="headerlink" title="编译、运行与发布"></a>编译、运行与发布</h2><p>之前用rustc来编译单个文件,现在我们来使用cargo构建整个项目,首先回到根目录 hello_cargo,输入命令</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo build</span><br></pre></td></tr></table></figure></div><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-03-24_21-59-03.jpg" alt="img" ></p><p>编译完成如上图,我们就可以发现,目录里生成了一个target文件夹,在<code> ./targe/debug</code> 目录下,就可以找到我们编译成功的可执行文件了,同样运行它,就能看到 hello world了</p><p>PS:如果想要编译+运行,可以使用 <code>cargo run</code> 命令(若源代码未发生改变,cargo run不会重新进行构建,而是直接运行)</p><p>另外,cargo还有一个比较好用的命令 <code>cargo check</code> ,这个命令可以让你在编译大型程序的时候,免去漫长的编译等待,快速的知道代码能否完成编译(真的这么神吗,cmake编译一个小时opencv最后环境出错编译失败的我如此问道)</p><hr><p>如果你已经准备好发布你的程序了,那么就可以用 <code>cargo build --release</code> 来编译代码,它会在 <code>target/release</code> 的目录下生成可执行文件,和debug不同的是,它会花更长的编译时间来优化你的代码,使代码有更好的运行性能。也就是说,普通的build侧重于快速的编译,让你调试程序,release build侧重于可执行文件的运行性能,用来交付给用户。</p><p><img lazyload src="/images/loading.svg" data-src="/images/Snipaste_2023-03-24_22-09-48.jpg" alt="img" ></p><p>可以看到编译后有一个optimized的标签,表示是优化过的target,同时也少了debug info。(但编译速度更快了,应该就是我之前debug编译过一次,基于那个的基础上,又花了0.84s进行优化)</p><p>PS:同理,你想编译完直接测试,可以用 <code>cargo run --release</code></p><p>这么看来,cargo应该就是rust构建项目的核心工具了。</p><p>到此为止,第一章结束。</p>]]></content>
<summary type="html">
<div class="note p-4 mb-4 rounded-small info">
<p>本系列参考书目:《RUST权威指南》</p>
</div>
<p>久闻rust大名,趁着研究生还是学习生涯,抽空出来试试这个所谓的既高效,又安全的语言</p></summary>
<category term="编程/语言" scheme="https://twosix.page/categories/%E7%BC%96%E7%A8%8B-%E8%AF%AD%E8%A8%80/"/>
<category term="Rust" scheme="https://twosix.page/tags/Rust/"/>
</entry>
</feed>