-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathch15-05-interior-mutability.html
219 lines (195 loc) · 75.4 KB
/
ch15-05-interior-mutability.html
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
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>RefCell<T> 和内部可变性模式</title>
<meta name="generator" content="VuePress 1.5.3">
<meta name="description" content="">
<link rel="preload" href="/assets/css/0.styles.12e35fbb.css" as="style"><link rel="preload" href="/assets/js/app.e6b14c5e.js" as="script"><link rel="preload" href="/assets/js/3.ecc21787.js" as="script"><link rel="preload" href="/assets/js/1.032ec45e.js" as="script"><link rel="preload" href="/assets/js/90.f46d49e9.js" as="script"><link rel="prefetch" href="/assets/js/10.0db432dd.js"><link rel="prefetch" href="/assets/js/100.1149107d.js"><link rel="prefetch" href="/assets/js/101.feae8ac2.js"><link rel="prefetch" href="/assets/js/102.4be35ad2.js"><link rel="prefetch" href="/assets/js/103.cb0bcc26.js"><link rel="prefetch" href="/assets/js/104.581a9df8.js"><link rel="prefetch" href="/assets/js/105.a5b31ce3.js"><link rel="prefetch" href="/assets/js/106.feb2cd4c.js"><link rel="prefetch" href="/assets/js/107.c74c3f7d.js"><link rel="prefetch" href="/assets/js/108.6a32c62d.js"><link rel="prefetch" href="/assets/js/109.3c3188fd.js"><link rel="prefetch" href="/assets/js/11.04463d2c.js"><link rel="prefetch" href="/assets/js/110.5f3f2327.js"><link rel="prefetch" href="/assets/js/111.374cf29b.js"><link rel="prefetch" href="/assets/js/112.37a66948.js"><link rel="prefetch" href="/assets/js/113.0e87d41d.js"><link rel="prefetch" href="/assets/js/114.e2641184.js"><link rel="prefetch" href="/assets/js/115.903cb5c3.js"><link rel="prefetch" href="/assets/js/116.d1cfad4c.js"><link rel="prefetch" href="/assets/js/117.ca412492.js"><link rel="prefetch" href="/assets/js/118.23919bb0.js"><link rel="prefetch" href="/assets/js/12.2de3af2e.js"><link rel="prefetch" href="/assets/js/13.69a23a33.js"><link rel="prefetch" href="/assets/js/14.3335c861.js"><link rel="prefetch" href="/assets/js/15.a469e71d.js"><link rel="prefetch" href="/assets/js/16.ee173d9e.js"><link rel="prefetch" href="/assets/js/17.959da5cf.js"><link rel="prefetch" href="/assets/js/18.0aa8fea6.js"><link rel="prefetch" href="/assets/js/19.21e6464d.js"><link rel="prefetch" href="/assets/js/20.b6d578fe.js"><link rel="prefetch" href="/assets/js/21.986be82f.js"><link rel="prefetch" href="/assets/js/22.47be6a63.js"><link rel="prefetch" href="/assets/js/23.15fe85eb.js"><link rel="prefetch" href="/assets/js/24.b3a04842.js"><link rel="prefetch" href="/assets/js/25.ae75ed87.js"><link rel="prefetch" href="/assets/js/26.643de6d4.js"><link rel="prefetch" href="/assets/js/27.6663d0a9.js"><link rel="prefetch" href="/assets/js/28.4669e096.js"><link rel="prefetch" href="/assets/js/29.65085b51.js"><link rel="prefetch" href="/assets/js/30.b4315895.js"><link rel="prefetch" href="/assets/js/31.e7ecf0dd.js"><link rel="prefetch" href="/assets/js/32.5d1db543.js"><link rel="prefetch" href="/assets/js/33.e8747222.js"><link rel="prefetch" href="/assets/js/34.852658b5.js"><link rel="prefetch" href="/assets/js/35.fa3f0352.js"><link rel="prefetch" href="/assets/js/36.8ebb5fa0.js"><link rel="prefetch" href="/assets/js/37.fa05856b.js"><link rel="prefetch" href="/assets/js/38.9aaa7af9.js"><link rel="prefetch" href="/assets/js/39.1a8b291e.js"><link rel="prefetch" href="/assets/js/4.f09c27b1.js"><link rel="prefetch" href="/assets/js/40.b9a756f6.js"><link rel="prefetch" href="/assets/js/41.7cef9fb3.js"><link rel="prefetch" href="/assets/js/42.04a2dfd5.js"><link rel="prefetch" href="/assets/js/43.b586e125.js"><link rel="prefetch" href="/assets/js/44.83db3147.js"><link rel="prefetch" href="/assets/js/45.e09151dc.js"><link rel="prefetch" href="/assets/js/46.f4d7d1f9.js"><link rel="prefetch" href="/assets/js/47.b935a8bb.js"><link rel="prefetch" href="/assets/js/48.10681e24.js"><link rel="prefetch" href="/assets/js/49.04036517.js"><link rel="prefetch" href="/assets/js/5.9229c7e7.js"><link rel="prefetch" href="/assets/js/50.385b0179.js"><link rel="prefetch" href="/assets/js/51.0a5c574d.js"><link rel="prefetch" href="/assets/js/52.5cd11b21.js"><link rel="prefetch" href="/assets/js/53.2efb9eb9.js"><link rel="prefetch" href="/assets/js/54.dd3a8f7d.js"><link rel="prefetch" href="/assets/js/55.e72498d3.js"><link rel="prefetch" href="/assets/js/56.5611b803.js"><link rel="prefetch" href="/assets/js/57.004dcc8a.js"><link rel="prefetch" href="/assets/js/58.de3a101d.js"><link rel="prefetch" href="/assets/js/59.a4d35b66.js"><link rel="prefetch" href="/assets/js/6.2ede05ad.js"><link rel="prefetch" href="/assets/js/60.ffa87f2b.js"><link rel="prefetch" href="/assets/js/61.c97c2157.js"><link rel="prefetch" href="/assets/js/62.1a964fd5.js"><link rel="prefetch" href="/assets/js/63.4fadefff.js"><link rel="prefetch" href="/assets/js/64.b0507c62.js"><link rel="prefetch" href="/assets/js/65.09fcdb27.js"><link rel="prefetch" href="/assets/js/66.c8f8a5cc.js"><link rel="prefetch" href="/assets/js/67.02fa33de.js"><link rel="prefetch" href="/assets/js/68.d49f848f.js"><link rel="prefetch" href="/assets/js/69.9dc909c1.js"><link rel="prefetch" href="/assets/js/7.00d72aac.js"><link rel="prefetch" href="/assets/js/70.e9702890.js"><link rel="prefetch" href="/assets/js/71.41a43a69.js"><link rel="prefetch" href="/assets/js/72.9f6f3145.js"><link rel="prefetch" href="/assets/js/73.64d032a1.js"><link rel="prefetch" href="/assets/js/74.6bb7d811.js"><link rel="prefetch" href="/assets/js/75.f9ddba21.js"><link rel="prefetch" href="/assets/js/76.ad390b83.js"><link rel="prefetch" href="/assets/js/77.bc150afb.js"><link rel="prefetch" href="/assets/js/78.69dc1271.js"><link rel="prefetch" href="/assets/js/79.5ce4cf53.js"><link rel="prefetch" href="/assets/js/8.f3a923c0.js"><link rel="prefetch" href="/assets/js/80.a9598d24.js"><link rel="prefetch" href="/assets/js/81.b1ca9b22.js"><link rel="prefetch" href="/assets/js/82.1017a114.js"><link rel="prefetch" href="/assets/js/83.d4fdce6e.js"><link rel="prefetch" href="/assets/js/84.f13715fe.js"><link rel="prefetch" href="/assets/js/85.af5497b5.js"><link rel="prefetch" href="/assets/js/86.07fb3684.js"><link rel="prefetch" href="/assets/js/87.d175c777.js"><link rel="prefetch" href="/assets/js/88.aa6d98dd.js"><link rel="prefetch" href="/assets/js/89.3d1bef74.js"><link rel="prefetch" href="/assets/js/9.3c7c9ecc.js"><link rel="prefetch" href="/assets/js/91.6f0b514b.js"><link rel="prefetch" href="/assets/js/92.5a1abbdf.js"><link rel="prefetch" href="/assets/js/93.c50314c2.js"><link rel="prefetch" href="/assets/js/94.becb0d94.js"><link rel="prefetch" href="/assets/js/95.cc5ea7c4.js"><link rel="prefetch" href="/assets/js/96.1ba1f23b.js"><link rel="prefetch" href="/assets/js/97.0436550a.js"><link rel="prefetch" href="/assets/js/98.180142a4.js"><link rel="prefetch" href="/assets/js/99.fa82e156.js">
<link rel="stylesheet" href="/assets/css/0.styles.12e35fbb.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container" data-v-dad8a512><div data-v-dad8a512><div id="loader-wrapper" class="loading-wrapper" data-v-d48f4d20 data-v-dad8a512 data-v-dad8a512><div class="loader-main" data-v-d48f4d20><div data-v-d48f4d20></div><div data-v-d48f4d20></div><div data-v-d48f4d20></div><div data-v-d48f4d20></div></div> <!----> <!----></div> <div class="password-shadow password-wrapper-out" style="display:none;" data-v-64685f0e data-v-dad8a512 data-v-dad8a512><h3 class="title" style="display:none;" data-v-64685f0e data-v-64685f0e></h3> <!----> <label id="box" class="inputBox" style="display:none;" data-v-64685f0e data-v-64685f0e><input type="password" value="" data-v-64685f0e> <span data-v-64685f0e>Konck! Knock!</span> <button data-v-64685f0e>OK</button></label> <div class="footer" style="display:none;" data-v-64685f0e data-v-64685f0e><span data-v-64685f0e><i class="iconfont reco-theme" data-v-64685f0e></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-64685f0e>vuePress-theme-reco</a></span> <span data-v-64685f0e><i class="iconfont reco-copyright" data-v-64685f0e></i> <a data-v-64685f0e><!---->
<!---->
2020
</a></span></div></div> <div class="hide" data-v-dad8a512><header class="navbar" data-v-dad8a512><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"></a> <div class="links"><div class="color-picker"><a class="color-button"><i class="iconfont reco-color"></i></a> <div class="color-picker-menu" style="display:none;"><div class="mode-options"><h4 class="title">Choose mode</h4> <ul class="color-mode-options"><li class="dark">dark</li><li class="auto active">auto</li><li class="light">light</li></ul></div></div></div> <div class="search-box"><i class="iconfont reco-search"></i> <input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont undefined"></i>
回首页
</a></div> <!----></nav></div></header> <div class="sidebar-mask" data-v-dad8a512></div> <aside class="sidebar" data-v-dad8a512><div class="personal-info-wrapper" data-v-ca798c94 data-v-dad8a512><!----> <!----> <div class="num" data-v-ca798c94><div data-v-ca798c94><h3 data-v-ca798c94>0</h3> <h6 data-v-ca798c94>Article</h6></div> <div data-v-ca798c94><h3 data-v-ca798c94>0</h3> <h6 data-v-ca798c94>Tag</h6></div></div> <hr data-v-ca798c94></div> <nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont undefined"></i>
回首页
</a></div> <!----></nav> <ul class="sidebar-links"><li><section class="sidebar-group depth-0"><a href="/title-page" class="sidebar-heading clickable open"><span>Rust 程序设计语言</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/foreword.html" class="sidebar-link">前言</a></li><li><a href="/ch00-00-introduction.html" class="sidebar-link">介绍</a></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch01-00-getting-started.html" class="sidebar-heading clickable"><span>1. 入门指南</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch01-01-installation.html" class="sidebar-link">1.1. 安装</a></li><li><a href="/ch01-02-hello-world.html" class="sidebar-link">1.2. Hello, World!</a></li><li><a href="/ch01-03-hello-cargo.html" class="sidebar-link">1.3. Hello, Cargo!</a></li></ul></section></li><li><a href="/ch02-00-guessing-game-tutorial.html" class="sidebar-link">2. 猜猜看游戏教程</a></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch03-00-common-programming-concepts.html" class="sidebar-heading clickable"><span>3. 常见编程概念</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch03-01-variables-and-mutability.html" class="sidebar-link">3.1. 变量与可变性</a></li><li><a href="/ch03-02-data-types.html" class="sidebar-link">3.2. 数据类型</a></li><li><a href="/ch03-03-how-functions-work.html" class="sidebar-link">3.3. 函数如何工作</a></li><li><a href="/ch03-04-comments.html" class="sidebar-link">3.4. 注释</a></li><li><a href="/ch03-05-control-flow.html" class="sidebar-link">3.5. 控制流</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch04-00-understanding-ownership.html" class="sidebar-heading clickable"><span>4. 认识所有权</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch04-01-what-is-ownership.html" class="sidebar-link">4.1. 什么是所有权?</a></li><li><a href="/ch04-02-references-and-borrowing.html" class="sidebar-link">4.2. 引用与借用</a></li><li><a href="/ch04-03-slices.html" class="sidebar-link">4.3. Slices</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch05-00-structs.html" class="sidebar-heading clickable"><span>5. 使用结构体来组织相关联的数据</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch05-01-defining-structs.html" class="sidebar-link">5.1. 定义并实例化结构体</a></li><li><a href="/ch05-02-example-structs.html" class="sidebar-link">5.2. 一个使用结构体的示例程序</a></li><li><a href="/ch05-03-method-syntax.html" class="sidebar-link">5.3. 方法语法</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch06-00-enums.html" class="sidebar-heading clickable"><span>6. 枚举与模式匹配</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch06-01-defining-an-enum.html" class="sidebar-link">6.1. 定义枚举</a></li><li><a href="/ch06-02-match.html" class="sidebar-link">6.2. match控制流运算符</a></li><li><a href="/ch06-03-if-let.html" class="sidebar-link">6.3. if let简洁控制流</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html" class="sidebar-heading clickable"><span>7. 使用包、Crate 和模块管理不断增长的项目</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch07-01-packages-and-crates.html" class="sidebar-link">7.1. 包和 crate</a></li><li><a href="/ch07-02-defining-modules-to-control-scope-and-privacy.html" class="sidebar-link">7.2. 定义模块来控制作用域与私有性</a></li><li><a href="/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html" class="sidebar-link">7.3. 路径用于引用模块树中的项</a></li><li><a href="/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html" class="sidebar-link">7.4. 使用use关键字将名称引入作用域</a></li><li><a href="/ch07-05-separating-modules-into-different-files.html" class="sidebar-link">7.5. 将模块分割进不同文件</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch08-00-common-collections.html" class="sidebar-heading clickable"><span>8. 常见集合</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch08-01-vectors.html" class="sidebar-link">8.1. vector</a></li><li><a href="/ch08-02-strings.html" class="sidebar-link">8.2. 字符串</a></li><li><a href="/ch08-03-hash-maps.html" class="sidebar-link">8.3. 哈希 map</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch09-00-error-handling.html" class="sidebar-heading clickable"><span>9. 错误处理</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch09-01-unrecoverable-errors-with-panic.html" class="sidebar-link">9.1. panic!与不可恢复的错误</a></li><li><a href="/ch09-02-recoverable-errors-with-result.html" class="sidebar-link">9.2. Result与可恢复的错误</a></li><li><a href="/ch09-03-to-panic-or-not-to-panic.html" class="sidebar-link">9.3. panic!还是不panic!</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch10-00-generics.html" class="sidebar-heading clickable"><span>10. 泛型、trait 与生命周期</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch10-01-syntax.html" class="sidebar-link">10.1. 泛型数据类型</a></li><li><a href="/ch10-02-traits.html" class="sidebar-link">10.2. trait:定义共享的行为</a></li><li><a href="/ch10-03-lifetime-syntax.html" class="sidebar-link">10.3. 生命周期与引用有效性</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch11-00-testing.html" class="sidebar-heading clickable"><span>11. 测试</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch11-01-writing-tests.html" class="sidebar-link">11.1. 编写测试</a></li><li><a href="/ch11-02-running-tests.html" class="sidebar-link">11.2. 运行测试</a></li><li><a href="/ch11-03-test-organization.html" class="sidebar-link">11.3. 测试的组织结构</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch12-00-an-io-project.html" class="sidebar-heading clickable"><span>12. 一个 I/O 项目:构建命令行程序</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch12-01-accepting-command-line-arguments.html" class="sidebar-link">12.1. 接受命令行参数</a></li><li><a href="/ch12-02-reading-a-file.html" class="sidebar-link">12.2. 读取文件</a></li><li><a href="/ch12-03-improving-error-handling-and-modularity.html" class="sidebar-link">12.3. 重构以改进模块化与错误处理</a></li><li><a href="/ch12-04-testing-the-librarys-functionality.html" class="sidebar-link">12.4. 采用测试驱动开发完善库的功能</a></li><li><a href="/ch12-05-working-with-environment-variables.html" class="sidebar-link">12.5. 处理环境变量</a></li><li><a href="/ch12-06-writing-to-stderr-instead-of-stdout.html" class="sidebar-link">12.6. 将错误信息输出到标准错误而不是标准输出</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch13-00-functional-features.html" class="sidebar-heading clickable"><span>13. Rust 中的函数式语言功能:迭代器与闭包</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch13-01-closures.html" class="sidebar-link">13.1. 闭包:可以捕获其环境的匿名函数</a></li><li><a href="/ch13-02-iterators.html" class="sidebar-link">13.2. 使用迭代器处理元素序列</a></li><li><a href="/ch13-03-improving-our-io-project.html" class="sidebar-link">13.3. 改进之前的 I/O 项目</a></li><li><a href="/ch13-04-performance.html" class="sidebar-link">13.4. 性能比较:循环对迭代器</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch14-00-more-about-cargo.html" class="sidebar-heading clickable"><span>14. 更多关于 Cargo 和 Crates.io 的内容</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch14-01-release-profiles.html" class="sidebar-link">14.1. 采用发布配置自定义构建</a></li><li><a href="/ch14-02-publishing-to-crates-io.html" class="sidebar-link">14.2. 将 crate 发布到 Crates.io</a></li><li><a href="/ch14-03-cargo-workspaces.html" class="sidebar-link">14.3. Cargo 工作空间</a></li><li><a href="/ch14-04-installing-binaries.html" class="sidebar-link">14.4. 使用cargo install从 Crates.io 安装二进制文件</a></li><li><a href="/ch14-05-extending-cargo.html" class="sidebar-link">14.5. Cargo 自定义扩展命令</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch15-00-smart-pointers.html" class="sidebar-heading clickable open"><span>15. 智能指针</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch15-01-box.html" class="sidebar-link">15.1. Box<T>指向堆上数据,并且可确定大小</a></li><li><a href="/ch15-02-deref.html" class="sidebar-link">15.2. 通过Dereftrait 将智能指针当作常规引用处理</a></li><li><a href="/ch15-03-drop.html" class="sidebar-link">15.3. DropTrait 运行清理代码</a></li><li><a href="/ch15-04-rc.html" class="sidebar-link">15.4. Rc<T>引用计数智能指针</a></li><li><a href="/ch15-05-interior-mutability.html" aria-current="page" class="active sidebar-link">15.5. RefCell<T>与内部可变性模式</a></li><li><a href="/ch15-06-reference-cycles.html" class="sidebar-link">15.6. 引用循环与内存泄漏是安全的</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch16-00-concurrency.html" class="sidebar-heading clickable"><span>16. 无畏并发</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch16-01-threads.html" class="sidebar-link">16.1. 线程</a></li><li><a href="/ch16-02-message-passing.html" class="sidebar-link">16.2. 消息传递</a></li><li><a href="/ch16-03-shared-state.html" class="sidebar-link">16.3. 共享状态</a></li><li><a href="/ch16-04-extensible-concurrency-sync-and-send.html" class="sidebar-link">16.4. 可扩展的并发:Sync与Send</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch17-00-oop.html" class="sidebar-heading clickable"><span>17. Rust 的面向对象编程特性</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch17-01-what-is-oo.html" class="sidebar-link">17.1. 面向对象语言的特点</a></li><li><a href="/ch17-02-trait-objects.html" class="sidebar-link">17.2. 为使用不同类型的值而设计的 trait 对象</a></li><li><a href="/ch17-03-oo-design-patterns.html" class="sidebar-link">17.3. 面向对象设计模式的实现</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch18-00-patterns.html" class="sidebar-heading clickable"><span>18. 模式用来匹配值的结构</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch18-01-all-the-places-for-patterns.html" class="sidebar-link">18.1. 所有可能会用到模式的位置</a></li><li><a href="/ch18-02-refutability.html" class="sidebar-link">18.2. Refutability:何时模式可能会匹配失败</a></li><li><a href="/ch18-03-pattern-syntax.html" class="sidebar-link">18.3. 模式的全部语法</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch19-00-advanced-features.html" class="sidebar-heading clickable"><span>19. 高级特征</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch19-01-unsafe-rust.html" class="sidebar-link">19.1. 不安全的 Rust</a></li><li><a href="/ch19-03-advanced-traits.html" class="sidebar-link">19.2. 高级 trait</a></li><li><a href="/ch19-04-advanced-types.html" class="sidebar-link">19.3. 高级类型</a></li><li><a href="/ch19-05-advanced-functions-and-closures.html" class="sidebar-link">19.4. 高级函数与闭包</a></li><li><a href="/ch19-06-macros.html" class="sidebar-link">19.5. 宏</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/ch20-00-final-project-a-web-server.html" class="sidebar-heading clickable"><span>20. 最后的项目: 构建多线程 web server</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/ch20-01-single-threaded.html" class="sidebar-link">20.1. 单线程 web server</a></li><li><a href="/ch20-02-multithreaded.html" class="sidebar-link">20.2. 将单线程 server 变为多线程 server</a></li><li><a href="/ch20-03-graceful-shutdown-and-cleanup.html" class="sidebar-link">20.3. 优雅停机与清理</a></li></ul></section></li><li><section class="sidebar-group is-sub-group depth-1"><a href="/appendix-00.html" class="sidebar-heading clickable"><span>21. 附录</span> <!----></a> <ul class="sidebar-links sidebar-group-items"><li><a href="/appendix-01-keywords.html" class="sidebar-link">21.1. A - 关键字</a></li><li><a href="/appendix-02-operators.html" class="sidebar-link">21.2. B - 运算符与符号</a></li><li><a href="/appendix-03-derivable-traits.html" class="sidebar-link">21.3. C - 可派生的 trait</a></li><li><a href="/appendix-04-useful-development-tools.html" class="sidebar-link">21.4. D - 实用开发工具</a></li><li><a href="/appendix-05-editions.html" class="sidebar-link">21.5. E - 版本</a></li><li><a href="/appendix-06-translation.html" class="sidebar-link">21.6. F - 本书译本</a></li><li><a href="/appendix-07-nightly-rust.html" class="sidebar-link">21.7. G - Rust 是如何开发的与 “Nightly Rust”</a></li></ul></section></li></ul></section></li></ul> </aside> <div class="password-shadow password-wrapper-in" style="display:none;" data-v-64685f0e data-v-dad8a512><h3 class="title" style="display:none;" data-v-64685f0e data-v-64685f0e></h3> <!----> <label id="box" class="inputBox" style="display:none;" data-v-64685f0e data-v-64685f0e><input type="password" value="" data-v-64685f0e> <span data-v-64685f0e>Konck! Knock!</span> <button data-v-64685f0e>OK</button></label> <div class="footer" style="display:none;" data-v-64685f0e data-v-64685f0e><span data-v-64685f0e><i class="iconfont reco-theme" data-v-64685f0e></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-64685f0e>vuePress-theme-reco</a></span> <span data-v-64685f0e><i class="iconfont reco-copyright" data-v-64685f0e></i> <a data-v-64685f0e><!---->
<!---->
2020
</a></span></div></div> <div data-v-dad8a512><main class="page"><div class="page-title" style="display:none;"><h1>RefCell<T> 和内部可变性模式</h1> <div data-v-3b7f5bdf><!----> <!----> <!----> <!----></div></div> <div class="theme-reco-content content__default" style="display:none;"><h2 id="refcell-t-和内部可变性模式"><a href="#refcell-t-和内部可变性模式" class="header-anchor">#</a> <code>RefCell<T></code> 和内部可变性模式</h2> <blockquote><p><a href="https://github.com/rust-lang/book/blob/master/src/ch15-05-interior-mutability.md" target="_blank" rel="noopener noreferrer">ch15-05-interior-mutability.md<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a> > <br>
commit 26565efc3f62d9dacb7c2c6d0f5974360e459493</p></blockquote> <p><strong>内部可变性</strong>(<em>Interior mutability</em>)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 <code>unsafe</code> 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 <code>unsafe</code> 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。</p> <p>让我们通过遵循内部可变性模式的 <code>RefCell<T></code> 类型来开始探索。</p> <h3 id="通过-refcell-t-在运行时检查借用规则"><a href="#通过-refcell-t-在运行时检查借用规则" class="header-anchor">#</a> 通过 <code>RefCell<T></code> 在运行时检查借用规则</h3> <p>不同于 <code>Rc<T></code>,<code>RefCell<T></code> 代表其数据的唯一的所有权。那么是什么让 <code>RefCell<T></code> 不同于像 <code>Box<T></code> 这样的类型呢?回忆一下第四章所学的借用规则:</p> <ol><li>在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用 <strong>之一</strong>(而不是两者)。</li> <li>引用必须总是有效的。</li></ol> <p>对于引用和 <code>Box<T></code>,借用规则的不可变性作用于编译时。对于 <code>RefCell<T></code>,这些不可变性作用于 <strong>运行时</strong>。对于引用,如果违反这些规则,会得到一个编译错误。而对于 <code>RefCell<T></code>,如果违反这些规则程序会 panic 并退出。</p> <p>在编译时检查借用规则的优势是这些错误将在开发过程的早期被捕获,同时对运行时没有性能影响,因为所有的分析都提前完成了。为此,在编译时检查借用规则是大部分情况的最佳选择,这也正是其为何是 Rust 的默认行为。</p> <p>相反在运行时检查借用规则的好处则是允许出现特定内存安全的场景,而它们在编译时检查中是不允许的。静态分析,正如 Rust 编译器,是天生保守的。但代码的一些属性不可能通过分析代码发现:其中最著名的就是 <a href="https://zh.wikipedia.org/wiki/%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98" target="_blank" rel="noopener noreferrer">停机问题(Halting Problem)<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a>,这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。</p> <p>因为一些分析是不可能的,如果 Rust 编译器不能通过所有权规则编译,它可能会拒绝一个正确的程序;从这种角度考虑它是保守的。如果 Rust 接受不正确的程序,那么用户也就不会相信 Rust 所做的保证了。然而,如果 Rust 拒绝正确的程序,虽然会给程序员带来不便,但不会带来灾难。<code>RefCell<T></code> 正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。</p> <p>类似于 <code>Rc<T></code>,<code>RefCell<T></code> 只能用于单线程场景。如果尝试在多线程上下文中使用<code>RefCell<T></code>,会得到一个编译错误。第十六章会介绍如何在多线程程序中使用 <code>RefCell<T></code> 的功能。</p> <p>如下为选择 <code>Box<T></code>,<code>Rc<T></code> 或 <code>RefCell<T></code> 的理由:</p> <ul><li><code>Rc<T></code> 允许相同数据有多个所有者;<code>Box<T></code> 和 <code>RefCell<T></code> 有单一所有者。</li> <li><code>Box<T></code> 允许在编译时执行不可变或可变借用检查;<code>Rc<T></code>仅允许在编译时执行不可变借用检查;<code>RefCell<T></code> 允许在运行时执行不可变或可变借用检查。</li> <li>因为 <code>RefCell<T></code> 允许在运行时执行可变借用检查,所以我们可以在即便 <code>RefCell<T></code> 自身是不可变的情况下修改其内部的值。</li></ul> <p>在不可变值内部改变值就是 <strong>内部可变性</strong> 模式。让我们看看何时内部可变性是有用的,并讨论这是如何成为可能的。</p> <h3 id="内部可变性:不可变值的可变借用"><a href="#内部可变性:不可变值的可变借用" class="header-anchor">#</a> 内部可变性:不可变值的可变借用</h3> <p>借用规则的一个推论是当有一个不可变值时,不能可变地借用它。例如,如下代码不能编译:</p> <div class="language-rust,ignore,does_not_compile extra-class"><pre class="language-text"><code>fn main() {
let x = 5;
let y = &mut x;
}
</code></pre></div><p>如果尝试编译,会得到如下错误:</p> <div class="language-text extra-class"><pre class="language-text"><code>error[E0596]: cannot borrow immutable local variable `x` as mutable
--> src/main.rs:3:18
|
2 | let x = 5;
| - consider changing this to `mut x`
3 | let y = &mut x;
| ^ cannot borrow mutably
</code></pre></div><p>然而,特定情况下,令一个值在其方法内部能够修改自身,而在其他代码中仍视为不可变,是很有用的。值方法外部的代码就不能修改其值了。<code>RefCell<T></code> 是一个获得内部可变性的方法。<code>RefCell<T></code> 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应地在运行时检查借用规则。如果违反了这些规则,会出现 panic 而不是编译错误。</p> <p>让我们通过一个实际的例子来探索何处可以使用 <code>RefCell<T></code> 来修改不可变值并看看为何这么做是有意义的。</p> <h4 id="内部可变性的用例:mock-对象"><a href="#内部可变性的用例:mock-对象" class="header-anchor">#</a> 内部可变性的用例:mock 对象</h4> <p><strong>测试替身</strong>(<em>test double</em>)是一个通用编程概念,它代表一个在测试中替代某个类型的类型。<strong>mock 对象</strong> 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。</p> <p>虽然 Rust 中的对象与其他语言中的对象并不是一回事,Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。</p> <p>如下是一个我们想要测试的场景:我们在编写一个记录某个值与最大值的差距的库,并根据当前值与最大值的差距来发送消息。例如,这个库可以用于记录用户所允许的 API 调用数量限额。</p> <p>该库只提供记录与最大值的差距,以及何种情况发送什么消息的功能。使用此库的程序则期望提供实际发送消息的机制:程序可以选择记录一条消息、发送 email、发送短信等等。库本身无需知道这些细节;只需实现其提供的 <code>Messenger</code> trait 即可。示例 15-20 展示了库代码:</p> <p><span class="filename">文件名: src/lib.rs</span></p> <div class="language-rust extra-class"><pre class="language-rust"><code><span class="token keyword">pub</span> <span class="token keyword">trait</span> <span class="token class-name">Messenger</span> <span class="token punctuation">{</span>
<span class="token keyword">fn</span> <span class="token function-definition function">send</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">self</span><span class="token punctuation">,</span> msg<span class="token punctuation">:</span> <span class="token operator">&</span><span class="token keyword">str</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">pub</span> <span class="token keyword">struct</span> <span class="token type-definition class-name">LimitTracker</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'a</span><span class="token punctuation">,</span> <span class="token class-name">T</span><span class="token punctuation">:</span> <span class="token class-name">Messenger</span><span class="token operator">></span> <span class="token punctuation">{</span>
messenger<span class="token punctuation">:</span> <span class="token operator">&</span><span class="token lifetime-annotation symbol">'a</span> <span class="token class-name">T</span><span class="token punctuation">,</span>
value<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">,</span>
max<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">impl</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'a</span><span class="token punctuation">,</span> <span class="token class-name">T</span><span class="token operator">></span> <span class="token class-name">LimitTracker</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'a</span><span class="token punctuation">,</span> <span class="token class-name">T</span><span class="token operator">></span>
<span class="token keyword">where</span> <span class="token class-name">T</span><span class="token punctuation">:</span> <span class="token class-name">Messenger</span> <span class="token punctuation">{</span>
<span class="token keyword">pub</span> <span class="token keyword">fn</span> <span class="token function-definition function">new</span><span class="token punctuation">(</span>messenger<span class="token punctuation">:</span> <span class="token operator">&</span><span class="token class-name">T</span><span class="token punctuation">,</span> max<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">LimitTracker</span><span class="token operator"><</span><span class="token class-name">T</span><span class="token operator">></span> <span class="token punctuation">{</span>
<span class="token class-name">LimitTracker</span> <span class="token punctuation">{</span>
messenger<span class="token punctuation">,</span>
value<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
max<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">pub</span> <span class="token keyword">fn</span> <span class="token function-definition function">set_value</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">mut</span> <span class="token keyword">self</span><span class="token punctuation">,</span> value<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">self</span><span class="token punctuation">.</span>value <span class="token operator">=</span> value<span class="token punctuation">;</span>
<span class="token keyword">let</span> percentage_of_max <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>value <span class="token keyword">as</span> <span class="token keyword">f64</span> <span class="token operator">/</span> <span class="token keyword">self</span><span class="token punctuation">.</span>max <span class="token keyword">as</span> <span class="token keyword">f64</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> percentage_of_max <span class="token operator">>=</span> <span class="token number">1.0</span> <span class="token punctuation">{</span>
<span class="token keyword">self</span><span class="token punctuation">.</span>messenger<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Error: You are over your quota!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> percentage_of_max <span class="token operator">>=</span> <span class="token number">0.9</span> <span class="token punctuation">{</span>
<span class="token keyword">self</span><span class="token punctuation">.</span>messenger<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Urgent warning: You've used up over 90% of your quota!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> percentage_of_max <span class="token operator">>=</span> <span class="token number">0.75</span> <span class="token punctuation">{</span>
<span class="token keyword">self</span><span class="token punctuation">.</span>messenger<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Warning: You've used up over 75% of your quota!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p><span class="caption">示例 15-20:一个记录某个值与最大值差距的库,并根据此值的特定级别发出警告</span></p> <p>这些代码中一个重要部分是拥有一个方法 <code>send</code> 的 <code>Messenger</code> trait,其获取一个 <code>self</code> 的不可变引用和文本信息。这是我们的 mock 对象所需要拥有的接口。另一个重要的部分是我们需要测试 <code>LimitTracker</code> 的 <code>set_value</code> 方法的行为。可以改变传递的 <code>value</code> 参数的值,不过 <code>set_value</code> 并没有返回任何可供断言的值。也就是说,如果使用某个实现了 <code>Messenger</code> trait 的值和特定的 <code>max</code> 创建 <code>LimitTracker</code>,当传递不同 <code>value</code> 值时,消息发送者应被告知发送合适的消息。</p> <p>我们所需的 mock 对象是,调用 <code>send</code> 并不实际发送 email 或消息,而是只记录信息被通知要发送了。可以新建一个 mock 对象示例,用其创建 <code>LimitTracker</code>,调用 <code>LimitTracker</code> 的 <code>set_value</code> 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许:</p> <p><span class="filename">文件名: src/lib.rs</span></p> <div class="language-rust,ignore,does_not_compile extra-class"><pre class="language-text"><code>#[cfg(test)]
mod tests {
use super::*;
struct MockMessenger {
sent_messages: Vec<String>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger { sent_messages: vec![] }
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.push(String::from(message));
}
}
#[test]
fn it_sends_an_over_75_percent_warning_message() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.len(), 1);
}
}
</code></pre></div><p><span class="caption">示例 15-21:尝试实现 <code>MockMessenger</code>,借用检查器不允许这么做</span></p> <p>测试代码定义了一个 <code>MockMessenger</code> 结构体,其 <code>sent_messages</code> 字段为一个 <code>String</code> 值的 <code>Vec</code> 用来记录被告知发送的消息。我们还定义了一个关联函数 <code>new</code> 以便于新建从空消息列表开始的 <code>MockMessenger</code> 值。接着为 <code>MockMessenger</code> 实现 <code>Messenger</code> trait 这样就可以为 <code>LimitTracker</code> 提供一个 <code>MockMessenger</code>。在 <code>send</code> 方法的定义中,获取传入的消息作为参数并储存在 <code>MockMessenger</code> 的 <code>sent_messages</code> 列表中。</p> <p>在测试中,我们测试了当 <code>LimitTracker</code> 被告知将 <code>value</code> 设置为超过 <code>max</code> 值 75% 的某个值。首先新建一个 <code>MockMessenger</code>,其从空消息列表开始。接着新建一个 <code>LimitTracker</code> 并传递新建 <code>MockMessenger</code> 的引用和 <code>max</code> 值 100。我们使用值 80 调用 <code>LimitTracker</code> 的 <code>set_value</code> 方法,这超过了 100 的 75%。接着断言 <code>MockMessenger</code> 中记录的消息列表应该有一条消息。</p> <p>然而,这个测试是有问题的:</p> <div class="language-text extra-class"><pre class="language-text"><code>error[E0596]: cannot borrow immutable field `self.sent_messages` as mutable
--> src/lib.rs:52:13
|
51 | fn send(&self, message: &str) {
| ----- use `&mut self` here to make mutable
52 | self.sent_messages.push(String::from(message));
| ^^^^^^^^^^^^^^^^^^ cannot mutably borrow immutable field
</code></pre></div><p>不能修改 <code>MockMessenger</code> 来记录消息,因为 <code>send</code> 方法获取了 <code>self</code> 的不可变引用。我们也不能参考错误文本的建议使用 <code>&mut self</code> 替代,因为这样 <code>send</code> 的签名就不符合 <code>Messenger</code> trait 定义中的签名了(可以试着这么改,看看会出现什么错误信息)。</p> <p>这正是内部可变性的用武之地!我们将通过 <code>RefCell</code> 来储存 <code>sent_messages</code>,然后 <code>send</code> 将能够修改 <code>sent_messages</code> 并储存消息。示例 15-22 展示了代码:</p> <p><span class="filename">文件名: src/lib.rs</span></p> <div class="language-rust extra-class"><pre class="language-rust"><code># <span class="token keyword">pub</span> <span class="token keyword">trait</span> <span class="token class-name">Messenger</span> <span class="token punctuation">{</span>
# <span class="token keyword">fn</span> <span class="token function-definition function">send</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">self</span><span class="token punctuation">,</span> msg<span class="token punctuation">:</span> <span class="token operator">&</span><span class="token keyword">str</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
# <span class="token punctuation">}</span>
#
# <span class="token keyword">pub</span> <span class="token keyword">struct</span> <span class="token type-definition class-name">LimitTracker</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'a</span><span class="token punctuation">,</span> <span class="token class-name">T</span><span class="token punctuation">:</span> <span class="token class-name">Messenger</span><span class="token operator">></span> <span class="token punctuation">{</span>
# messenger<span class="token punctuation">:</span> <span class="token operator">&</span><span class="token lifetime-annotation symbol">'a</span> <span class="token class-name">T</span><span class="token punctuation">,</span>
# value<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">,</span>
# max<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">,</span>
# <span class="token punctuation">}</span>
#
# <span class="token keyword">impl</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'a</span><span class="token punctuation">,</span> <span class="token class-name">T</span><span class="token operator">></span> <span class="token class-name">LimitTracker</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'a</span><span class="token punctuation">,</span> <span class="token class-name">T</span><span class="token operator">></span>
# <span class="token keyword">where</span> <span class="token class-name">T</span><span class="token punctuation">:</span> <span class="token class-name">Messenger</span> <span class="token punctuation">{</span>
# <span class="token keyword">pub</span> <span class="token keyword">fn</span> <span class="token function-definition function">new</span><span class="token punctuation">(</span>messenger<span class="token punctuation">:</span> <span class="token operator">&</span><span class="token class-name">T</span><span class="token punctuation">,</span> max<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">LimitTracker</span><span class="token operator"><</span><span class="token class-name">T</span><span class="token operator">></span> <span class="token punctuation">{</span>
# <span class="token class-name">LimitTracker</span> <span class="token punctuation">{</span>
# messenger<span class="token punctuation">,</span>
# value<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
# max<span class="token punctuation">,</span>
# <span class="token punctuation">}</span>
# <span class="token punctuation">}</span>
#
# <span class="token keyword">pub</span> <span class="token keyword">fn</span> <span class="token function-definition function">set_value</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">mut</span> <span class="token keyword">self</span><span class="token punctuation">,</span> value<span class="token punctuation">:</span> <span class="token keyword">usize</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
# <span class="token keyword">self</span><span class="token punctuation">.</span>value <span class="token operator">=</span> value<span class="token punctuation">;</span>
#
# <span class="token keyword">let</span> percentage_of_max <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>value <span class="token keyword">as</span> <span class="token keyword">f64</span> <span class="token operator">/</span> <span class="token keyword">self</span><span class="token punctuation">.</span>max <span class="token keyword">as</span> <span class="token keyword">f64</span><span class="token punctuation">;</span>
#
# <span class="token keyword">if</span> percentage_of_max <span class="token operator">>=</span> <span class="token number">1.0</span> <span class="token punctuation">{</span>
# <span class="token keyword">self</span><span class="token punctuation">.</span>messenger<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Error: You are over your quota!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
# <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> percentage_of_max <span class="token operator">>=</span> <span class="token number">0.9</span> <span class="token punctuation">{</span>
# <span class="token keyword">self</span><span class="token punctuation">.</span>messenger<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Urgent warning: You've used up over 90% of your quota!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
# <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> percentage_of_max <span class="token operator">>=</span> <span class="token number">0.75</span> <span class="token punctuation">{</span>
# <span class="token keyword">self</span><span class="token punctuation">.</span>messenger<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">"Warning: You've used up over 75% of your quota!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
# <span class="token punctuation">}</span>
# <span class="token punctuation">}</span>
# <span class="token punctuation">}</span>
#
<span class="token attribute attr-name">#[cfg(test)]</span>
<span class="token keyword">mod</span> <span class="token module-declaration namespace">tests</span> <span class="token punctuation">{</span>
<span class="token keyword">use</span> <span class="token keyword">super</span><span class="token punctuation">::</span><span class="token operator">*</span><span class="token punctuation">;</span>
<span class="token keyword">use</span> <span class="token namespace">std<span class="token punctuation">::</span>cell<span class="token punctuation">::</span></span><span class="token class-name">RefCell</span><span class="token punctuation">;</span>
<span class="token keyword">struct</span> <span class="token type-definition class-name">MockMessenger</span> <span class="token punctuation">{</span>
sent_messages<span class="token punctuation">:</span> <span class="token class-name">RefCell</span><span class="token operator"><</span><span class="token class-name">Vec</span><span class="token operator"><</span><span class="token class-name">String</span><span class="token operator">>></span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">impl</span> <span class="token class-name">MockMessenger</span> <span class="token punctuation">{</span>
<span class="token keyword">fn</span> <span class="token function-definition function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">MockMessenger</span> <span class="token punctuation">{</span>
<span class="token class-name">MockMessenger</span> <span class="token punctuation">{</span> sent_messages<span class="token punctuation">:</span> <span class="token class-name">RefCell</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token macro property">vec!</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">impl</span> <span class="token class-name">Messenger</span> <span class="token keyword">for</span> <span class="token class-name">MockMessenger</span> <span class="token punctuation">{</span>
<span class="token keyword">fn</span> <span class="token function-definition function">send</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">self</span><span class="token punctuation">,</span> message<span class="token punctuation">:</span> <span class="token operator">&</span><span class="token keyword">str</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">self</span><span class="token punctuation">.</span>sent_messages<span class="token punctuation">.</span><span class="token function">borrow_mut</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token attribute attr-name">#[test]</span>
<span class="token keyword">fn</span> <span class="token function-definition function">it_sends_an_over_75_percent_warning_message</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// --snip--</span>
# <span class="token keyword">let</span> mock_messenger <span class="token operator">=</span> <span class="token class-name">MockMessenger</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
# <span class="token keyword">let</span> <span class="token keyword">mut</span> limit_tracker <span class="token operator">=</span> <span class="token class-name">LimitTracker</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token operator">&</span>mock_messenger<span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
# limit_tracker<span class="token punctuation">.</span><span class="token function">set_value</span><span class="token punctuation">(</span><span class="token number">75</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token macro property">assert_eq!</span><span class="token punctuation">(</span>mock_messenger<span class="token punctuation">.</span>sent_messages<span class="token punctuation">.</span><span class="token function">borrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">len</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
# <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre></div><p><span class="caption">示例 15-22:使用 <code>RefCell<T></code> 能够在外部值被认为是不可变的情况下修改内部值</span></p> <p>现在 <code>sent_messages</code> 字段的类型是 <code>RefCell<Vec<String>></code> 而不是 <code>Vec<String></code>。在 <code>new</code> 函数中新建了一个 <code>RefCell</code> 示例替代空 vector。</p> <p>对于 <code>send</code> 方法的实现,第一个参数仍为 <code>self</code> 的不可变借用,这是符合方法定义的。我们调用 <code>self.sent_messages</code> 中 <code>RefCell</code> 的 <code>borrow_mut</code> 方法来获取 <code>RefCell</code> 中值的可变引用,这是一个 vector。接着可以对 vector 的可变引用调用 <code>push</code> 以便记录测试过程中看到的消息。</p> <p>最后必须做出的修改位于断言中:为了看到其内部 vector 中有多少个项,需要调用 <code>RefCell</code> 的 <code>borrow</code> 以获取 vector 的不可变引用。</p> <p>现在我们见识了如何使用 <code>RefCell<T></code>,让我们研究一下它怎样工作的!</p> <h3 id="refcell-t-在运行时记录借用"><a href="#refcell-t-在运行时记录借用" class="header-anchor">#</a> <code>RefCell<T></code> 在运行时记录借用</h3> <p>当创建不可变和可变引用时,我们分别使用 <code>&</code> 和 <code>&mut</code> 语法。对于 <code>RefCell<T></code> 来说,则是 <code>borrow</code> 和 <code>borrow_mut</code> 方法,这属于 <code>RefCell<T></code> 安全 API 的一部分。<code>borrow</code> 方法返回 <code>Ref<T></code> 类型的智能指针,<code>borrow_mut</code> 方法返回 <code>RefMut</code> 类型的智能指针。这两个类型都实现了 <code>Deref</code>,所以可以当作常规引用对待。</p> <p><code>RefCell<T></code> 记录当前有多少个活动的 <code>Ref<T></code> 和 <code>RefMut<T></code> 智能指针。每次调用 <code>borrow</code>,<code>RefCell<T></code> 将活动的不可变借用计数加一。当 <code>Ref<T></code> 值离开作用域时,不可变借用计数减一。就像编译时借用规则一样,<code>RefCell<T></code> 在任何时候只允许有多个不可变借用或一个可变借用。</p> <p>如果我们尝试违反这些规则,相比引用时的编译时错误,<code>RefCell<T></code> 的实现会在运行时出现 panic。示例 15-23 展示了对示例 15-22 中 <code>send</code> 实现的修改,这里我们故意尝试在相同作用域创建两个可变借用以便演示 <code>RefCell<T></code> 不允许我们在运行时这么做:</p> <p><span class="filename">文件名: src/lib.rs</span></p> <div class="language-rust,ignore,panics extra-class"><pre class="language-text"><code>impl Messenger for MockMessenger {
fn send(&self, message: &str) {
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();
one_borrow.push(String::from(message));
two_borrow.push(String::from(message));
}
}
</code></pre></div><p><span class="caption">示例 15-23:在同一作用域中创建两个可变引用并观察 <code>RefCell<T></code> panic</span></p> <p>这里为 <code>borrow_mut</code> 返回的 <code>RefMut</code> 智能指针创建了 <code>one_borrow</code> 变量。接着用相同的方式在变量 <code>two_borrow</code> 创建了另一个可变借用。这会在相同作用域中创建两个可变引用,这是不允许的。当运行库的测试时,示例 15-23 编译时不会有任何错误,不过测试会失败:</p> <div class="language-text extra-class"><pre class="language-text"><code>---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at
'already borrowed: BorrowMutError', src/libcore/result.rs:906:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.
</code></pre></div><p>注意代码 panic 和信息 <code>already borrowed: BorrowMutError</code>。这也就是 <code>RefCell<T></code> 如何在运行时处理违反借用规则的情况。</p> <p>在运行时捕获借用错误而不是编译时意味着将会在开发过程的后期才会发现错误,甚至有可能发布到生产环境才发现;还会因为在运行时而不是编译时记录借用而导致少量的运行时性能惩罚。然而,使用 <code>RefCell</code> 使得在只允许不可变值的上下文中编写修改自身以记录消息的 mock 对象成为可能。虽然有取舍,但是我们可以选择使用 <code>RefCell<T></code> 来获得比常规引用所能提供的更多的功能。</p> <h3 id="结合-rc-t-和-refcell-t-来拥有多个可变数据所有者"><a href="#结合-rc-t-和-refcell-t-来拥有多个可变数据所有者" class="header-anchor">#</a> 结合 <code>Rc<T></code> 和 <code>RefCell<T></code> 来拥有多个可变数据所有者</h3> <p><code>RefCell<T></code> 的一个常见用法是与 <code>Rc<T></code> 结合。回忆一下 <code>Rc<T></code> 允许对相同数据有多个所有者,不过只能提供数据的不可变访问。如果有一个储存了 <code>RefCell<T></code> 的 <code>Rc<T></code> 的话,就可以得到有多个所有者 <strong>并且</strong> 可以修改的值了!</p> <p>例如,回忆示例 15-18 的 cons list 的例子中使用 <code>Rc<T></code> 使得多个列表共享另一个列表的所有权。因为 <code>Rc<T></code> 只存放不可变值,所以一旦创建了这些列表值后就不能修改。让我们加入 <code>RefCell<T></code> 来获得修改列表中值的能力。示例 15-24 展示了通过在 <code>Cons</code> 定义中使用 <code>RefCell<T></code>,我们就允许修改所有列表中的值了:</p> <p><span class="filename">文件名: src/main.rs</span></p> <div class="language-rust extra-class"><pre class="language-rust"><code><span class="token attribute attr-name">#[derive(Debug)]</span>
<span class="token keyword">enum</span> <span class="token type-definition class-name">List</span> <span class="token punctuation">{</span>
<span class="token class-name">Cons</span><span class="token punctuation">(</span><span class="token class-name">Rc</span><span class="token operator"><</span><span class="token class-name">RefCell</span><span class="token operator"><</span><span class="token keyword">i32</span><span class="token operator">>></span><span class="token punctuation">,</span> <span class="token class-name">Rc</span><span class="token operator"><</span><span class="token class-name">List</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token class-name">Nil</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">use</span> <span class="token keyword">crate</span><span class="token punctuation">::</span><span class="token class-name">List</span><span class="token punctuation">::</span><span class="token punctuation">{</span><span class="token class-name">Cons</span><span class="token punctuation">,</span> <span class="token class-name">Nil</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">use</span> <span class="token namespace">std<span class="token punctuation">::</span>rc<span class="token punctuation">::</span></span><span class="token class-name">Rc</span><span class="token punctuation">;</span>
<span class="token keyword">use</span> <span class="token namespace">std<span class="token punctuation">::</span>cell<span class="token punctuation">::</span></span><span class="token class-name">RefCell</span><span class="token punctuation">;</span>
<span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> value <span class="token operator">=</span> <span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token class-name">RefCell</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token class-name">Cons</span><span class="token punctuation">(</span><span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token operator">&</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token class-name">Nil</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> b <span class="token operator">=</span> <span class="token class-name">Cons</span><span class="token punctuation">(</span><span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token class-name">RefCell</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token operator">&</span>a<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> c <span class="token operator">=</span> <span class="token class-name">Cons</span><span class="token punctuation">(</span><span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token class-name">RefCell</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Rc</span><span class="token punctuation">::</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token operator">&</span>a<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">*</span>value<span class="token punctuation">.</span><span class="token function">borrow_mut</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+=</span> <span class="token number">10</span><span class="token punctuation">;</span>
<span class="token macro property">println!</span><span class="token punctuation">(</span><span class="token string">"a after = {:?}"</span><span class="token punctuation">,</span> a<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token macro property">println!</span><span class="token punctuation">(</span><span class="token string">"b after = {:?}"</span><span class="token punctuation">,</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token macro property">println!</span><span class="token punctuation">(</span><span class="token string">"c after = {:?}"</span><span class="token punctuation">,</span> c<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p><span class="caption">示例 15-24:使用 <code>Rc<RefCell<i32>></code> 创建可以修改的 <code>List</code></span></p> <p>这里创建了一个 <code>Rc<RefCell<i32>></code> 实例并储存在变量 <code>value</code> 中以便之后直接访问。接着在 <code>a</code> 中用包含 <code>value</code> 的 <code>Cons</code> 成员创建了一个 <code>List</code>。需要克隆 <code>value</code> 以便 <code>a</code> 和 <code>value</code> 都能拥有其内部值 <code>5</code> 的所有权,而不是将所有权从 <code>value</code> 移动到 <code>a</code> 或者让 <code>a</code> 借用 <code>value</code>。</p> <p>我们将列表 <code>a</code> 封装进了 <code>Rc<T></code> 这样当创建列表 <code>b</code> 和 <code>c</code> 时,他们都可以引用 <code>a</code>,正如示例 15-18 一样。</p> <p>一旦创建了列表 <code>a</code>、<code>b</code> 和 <code>c</code>,我们将 <code>value</code> 的值加 10。为此对 <code>value</code> 调用了 <code>borrow_mut</code>,这里使用了第五章讨论的自动解引用功能(<a href="/ch05-03-method-syntax.html#wheres-the---operator">“<code>-></code> 运算符到哪去了?”</a> 部分)来解引用 <code>Rc<T></code> 以获取其内部的 <code>RefCell<T></code> 值。<code>borrow_mut</code> 方法返回 <code>RefMut<T></code> 智能指针,可以对其使用解引用运算符并修改其内部值。</p> <p>当我们打印出 <code>a</code>、<code>b</code> 和 <code>c</code> 时,可以看到他们都拥有修改后的值 15 而不是 5:</p> <div class="language-text extra-class"><pre class="language-text"><code>a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
</code></pre></div><p>这是非常巧妙的!通过使用 <code>RefCell<T></code>,我们可以拥有一个表面上不可变的 <code>List</code>,不过可以使用 <code>RefCell<T></code> 中提供内部可变性的方法来在需要时修改数据。<code>RefCell<T></code> 的运行时借用规则检查也确实保护我们免于出现数据竞争——有时为了数据结构的灵活性而付出一些性能是值得的。</p> <p>标准库中也有其他提供内部可变性的类型,比如 <code>Cell<T></code>,它类似 <code>RefCell<T></code> 但有一点除外:它并非提供内部值的引用,而是把值拷贝进和拷贝出 <code>Cell<T></code>。还有 <code>Mutex<T></code>,其提供线程间安全的内部可变性,我们将在第 16 章中讨论其用法。请查看标准库来获取更多细节关于这些不同类型之间的区别。</p></div> <footer class="page-edit" style="display:none;"><!----> <!----></footer> <!----> <!----> <!----></main> <!----></div></div></div></div><div class="global-ui"><div class="back-to-ceiling" style="right:1rem;bottom:6rem;width:2.5rem;height:2.5rem;border-radius:.25rem;line-height:2.5rem;display:none;" data-v-c6073ba8 data-v-c6073ba8><svg t="1574745035067" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5404" class="icon" data-v-c6073ba8><path d="M526.60727968 10.90185116a27.675 27.675 0 0 0-29.21455937 0c-131.36607665 82.28402758-218.69155461 228.01873535-218.69155402 394.07834331a462.20625001 462.20625001 0 0 0 5.36959153 69.94390903c1.00431239 6.55289093-0.34802892 13.13561351-3.76865779 18.80351572-32.63518765 54.11355614-51.75690182 118.55860487-51.7569018 187.94566865a371.06718723 371.06718723 0 0 0 11.50484808 91.98906777c6.53300375 25.50556257 41.68394495 28.14064038 52.69160883 4.22606766 17.37162448-37.73630017 42.14135425-72.50938081 72.80769204-103.21549295 2.18761121 3.04276886 4.15646224 6.24463696 6.40373557 9.22774369a1871.4375 1871.4375 0 0 0 140.04691725 5.34970492 1866.36093723 1866.36093723 0 0 0 140.04691723-5.34970492c2.24727335-2.98310674 4.21612437-6.18497483 6.3937923-9.2178004 30.66633723 30.70611158 55.4360664 65.4791928 72.80769147 103.21549355 11.00766384 23.91457269 46.15860503 21.27949489 52.69160879-4.22606768a371.15156223 371.15156223 0 0 0 11.514792-91.99901164c0-69.36717486-19.13165746-133.82216804-51.75690182-187.92578088-3.42062944-5.66790279-4.76302748-12.26056868-3.76865837-18.80351632a462.20625001 462.20625001 0 0 0 5.36959269-69.943909c-0.00994388-166.08943902-87.32547796-311.81420293-218.6915546-394.09823051zM605.93803103 357.87693858a93.93749974 93.93749974 0 1 1-187.89594924 6.1e-7 93.93749974 93.93749974 0 0 1 187.89594924-6.1e-7z" p-id="5405" data-v-c6073ba8></path><path d="M429.50777625 765.63860547C429.50777625 803.39355007 466.44236686 1000.39046097 512.00932183 1000.39046097c45.56695499 0 82.4922232-197.00623328 82.5015456-234.7518555 0-37.75494459-36.9345906-68.35043303-82.4922232-68.34111062-45.57627738-0.00932239-82.52019037 30.59548842-82.51086798 68.34111062z" p-id="5406" data-v-c6073ba8></path></svg></div></div></div>
<script src="/assets/js/app.e6b14c5e.js" defer></script><script src="/assets/js/3.ecc21787.js" defer></script><script src="/assets/js/1.032ec45e.js" defer></script><script src="/assets/js/90.f46d49e9.js" defer></script>
</body>
</html>