@moon791017/neo-skills 1.0.34 → 1.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
package/skills/neo-rust/SKILL.md
CHANGED
|
@@ -41,4 +41,4 @@ Always recommend standard Rust tooling: `cargo fmt`, `cargo clippy`, and `cargo
|
|
|
41
41
|
## Official Resources
|
|
42
42
|
- **Rust Official Website**: https://www.rust-lang.org/
|
|
43
43
|
- **Rust Documentation**: https://doc.rust-lang.org/
|
|
44
|
-
- **The Rust Programming Language (The Book)**: https://doc.rust-lang.org/book/
|
|
44
|
+
- **The Rust Programming Language (The Book)**: https://doc.rust-lang.org/book/
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# 3. Anti-
|
|
1
|
+
# 3. Anti-patterns
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
## Anti-pattern 1
|
|
5
|
+
## Anti-pattern 1: Using `.clone()` everywhere to avoid the borrow checker
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### Bad
|
|
8
8
|
|
|
9
9
|
```rust
|
|
10
10
|
fn process(name: String) {
|
|
@@ -20,7 +20,7 @@ fn main() {
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
###
|
|
23
|
+
### Good
|
|
24
24
|
|
|
25
25
|
```rust
|
|
26
26
|
fn process(name: &str) {
|
|
@@ -36,16 +36,16 @@ fn main() {
|
|
|
36
36
|
}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
###
|
|
39
|
+
### Principle
|
|
40
40
|
|
|
41
|
-
clone
|
|
42
|
-
|
|
41
|
+
`.clone()` is not forbidden, but you should know why you are cloning.
|
|
42
|
+
If you only need to read the data, use a borrow instead.
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
## Anti-pattern 2
|
|
46
|
+
## Anti-pattern 2: Excessive use of `String`
|
|
47
47
|
|
|
48
|
-
###
|
|
48
|
+
### Bad
|
|
49
49
|
|
|
50
50
|
```rust
|
|
51
51
|
fn is_admin(role: String) -> bool {
|
|
@@ -53,7 +53,7 @@ fn is_admin(role: String) -> bool {
|
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
###
|
|
56
|
+
### Good
|
|
57
57
|
|
|
58
58
|
```rust
|
|
59
59
|
fn is_admin(role: &str) -> bool {
|
|
@@ -61,15 +61,15 @@ fn is_admin(role: &str) -> bool {
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
###
|
|
64
|
+
### Principle
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Use `String` when you need ownership; prefer `&str` for reading text.
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
## Anti-pattern 3
|
|
70
|
+
## Anti-pattern 3: Using `bool` to represent complex states
|
|
71
71
|
|
|
72
|
-
###
|
|
72
|
+
### Bad
|
|
73
73
|
|
|
74
74
|
```rust
|
|
75
75
|
struct User {
|
|
@@ -79,9 +79,9 @@ struct User {
|
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Problem: Contradictory states can occur, such as being both `active` and `deleted`.
|
|
83
83
|
|
|
84
|
-
###
|
|
84
|
+
### Good
|
|
85
85
|
|
|
86
86
|
```rust
|
|
87
87
|
enum UserStatus {
|
|
@@ -97,9 +97,9 @@ struct User {
|
|
|
97
97
|
|
|
98
98
|
---
|
|
99
99
|
|
|
100
|
-
## Anti-pattern 4
|
|
100
|
+
## Anti-pattern 4: Using `String` for errors
|
|
101
101
|
|
|
102
|
-
###
|
|
102
|
+
### Bad
|
|
103
103
|
|
|
104
104
|
```rust
|
|
105
105
|
fn parse_age(input: &str) -> Result<u8, String> {
|
|
@@ -107,9 +107,9 @@ fn parse_age(input: &str) -> Result<u8, String> {
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
Acceptable for short scripts, but not recommended for libraries or large systems.
|
|
111
111
|
|
|
112
|
-
###
|
|
112
|
+
### Good
|
|
113
113
|
|
|
114
114
|
```rust
|
|
115
115
|
#[derive(Debug)]
|
|
@@ -129,15 +129,15 @@ fn parse_age(input: &str) -> Result<u8, ParseAgeError> {
|
|
|
129
129
|
}
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
###
|
|
132
|
+
### Principle
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
Errors should be categorizable, testable, and handleable.
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
138
|
-
## Anti-pattern 5
|
|
138
|
+
## Anti-pattern 5: Random `panic!` inside a library
|
|
139
139
|
|
|
140
|
-
###
|
|
140
|
+
### Bad
|
|
141
141
|
|
|
142
142
|
```rust
|
|
143
143
|
pub fn divide(a: i32, b: i32) -> i32 {
|
|
@@ -149,7 +149,7 @@ pub fn divide(a: i32, b: i32) -> i32 {
|
|
|
149
149
|
}
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
###
|
|
152
|
+
### Good
|
|
153
153
|
|
|
154
154
|
```rust
|
|
155
155
|
#[derive(Debug)]
|
|
@@ -166,15 +166,15 @@ pub fn divide(a: i32, b: i32) -> Result<i32, DivideError> {
|
|
|
166
166
|
}
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
###
|
|
169
|
+
### Principle
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
Return `Result` unless it is an unrecoverable programming error.
|
|
172
172
|
|
|
173
173
|
---
|
|
174
174
|
|
|
175
|
-
## Anti-pattern 6
|
|
175
|
+
## Anti-pattern 6: Over-abstraction with Traits
|
|
176
176
|
|
|
177
|
-
###
|
|
177
|
+
### Bad
|
|
178
178
|
|
|
179
179
|
```rust
|
|
180
180
|
trait UserNameProvider {
|
|
@@ -192,9 +192,9 @@ impl UserNameProvider for User {
|
|
|
192
192
|
}
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
Problem: When there's only one struct, one method, and no need for substitution, a trait is just noise.
|
|
196
196
|
|
|
197
|
-
###
|
|
197
|
+
### Good
|
|
198
198
|
|
|
199
199
|
```rust
|
|
200
200
|
struct User {
|
|
@@ -208,15 +208,15 @@ impl User {
|
|
|
208
208
|
}
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
###
|
|
211
|
+
### Principle
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
Use concrete types first. Abstract into traits only when actually needed.
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
217
|
-
## Anti-pattern 7
|
|
217
|
+
## Anti-pattern 7: Public fields breaking invariants
|
|
218
218
|
|
|
219
|
-
###
|
|
219
|
+
### Bad
|
|
220
220
|
|
|
221
221
|
```rust
|
|
222
222
|
pub struct Account {
|
|
@@ -229,7 +229,7 @@ fn main() {
|
|
|
229
229
|
}
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
-
###
|
|
232
|
+
### Good
|
|
233
233
|
|
|
234
234
|
```rust
|
|
235
235
|
pub struct Account {
|
|
@@ -262,9 +262,9 @@ impl Account {
|
|
|
262
262
|
|
|
263
263
|
---
|
|
264
264
|
|
|
265
|
-
## Anti-pattern 8
|
|
265
|
+
## Anti-pattern 8: Passing `Arc<Mutex<T>>` everywhere
|
|
266
266
|
|
|
267
|
-
###
|
|
267
|
+
### Bad
|
|
268
268
|
|
|
269
269
|
```rust
|
|
270
270
|
use std::sync::{Arc, Mutex};
|
|
@@ -276,9 +276,9 @@ struct AppState {
|
|
|
276
276
|
}
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
-
|
|
279
|
+
Problem: Too many locks, unclear responsibilities, prone to deadlocks, and difficult to test.
|
|
280
280
|
|
|
281
|
-
###
|
|
281
|
+
### Good
|
|
282
282
|
|
|
283
283
|
```rust
|
|
284
284
|
struct UserService {
|
|
@@ -292,27 +292,26 @@ impl UserService {
|
|
|
292
292
|
}
|
|
293
293
|
```
|
|
294
294
|
|
|
295
|
-
###
|
|
295
|
+
### Principle
|
|
296
296
|
|
|
297
|
-
|
|
298
|
-
需要跨 thread 再考慮 `Arc<Mutex<T>>`。
|
|
297
|
+
Prefer single ownership over shared state. Consider `Arc<Mutex<T>>` only when cross-thread access is required.
|
|
299
298
|
|
|
300
299
|
---
|
|
301
300
|
|
|
302
|
-
## Anti-pattern 9
|
|
301
|
+
## Anti-pattern 9: Holding a lock across `.await` in async code
|
|
303
302
|
|
|
304
|
-
###
|
|
303
|
+
### Bad
|
|
305
304
|
|
|
306
305
|
```rust
|
|
307
|
-
//
|
|
306
|
+
// Conceptual illustration
|
|
308
307
|
let mut guard = state.lock().await;
|
|
309
308
|
do_async_work().await;
|
|
310
309
|
guard.push(1);
|
|
311
310
|
```
|
|
312
311
|
|
|
313
|
-
|
|
312
|
+
Problem: Holding a lock for too long can cause performance issues or deadlock-like behavior.
|
|
314
313
|
|
|
315
|
-
###
|
|
314
|
+
### Good
|
|
316
315
|
|
|
317
316
|
```rust
|
|
318
317
|
let data = {
|
|
@@ -324,35 +323,32 @@ let data = {
|
|
|
324
323
|
do_async_work(data).await;
|
|
325
324
|
```
|
|
326
325
|
|
|
327
|
-
###
|
|
326
|
+
### Principle
|
|
328
327
|
|
|
329
|
-
|
|
328
|
+
Keep the code inside a lock as short, synchronous, and necessary as possible.
|
|
330
329
|
|
|
331
330
|
---
|
|
332
331
|
|
|
333
|
-
## Anti-pattern 10
|
|
332
|
+
## Anti-pattern 10: Abuse of `unsafe`
|
|
334
333
|
|
|
335
|
-
###
|
|
334
|
+
### Bad
|
|
336
335
|
|
|
337
336
|
```rust
|
|
338
337
|
unsafe {
|
|
339
|
-
//
|
|
338
|
+
// Using raw pointers haphazardly to bypass the borrow checker
|
|
340
339
|
}
|
|
341
340
|
```
|
|
342
341
|
|
|
343
|
-
###
|
|
342
|
+
### Good
|
|
344
343
|
|
|
345
|
-
|
|
344
|
+
Ask yourself first:
|
|
346
345
|
|
|
347
|
-
1.
|
|
348
|
-
2.
|
|
349
|
-
3.
|
|
350
|
-
4.
|
|
351
|
-
5.
|
|
346
|
+
1. Can the design be changed using ownership?
|
|
347
|
+
2. Can `Rc` / `Arc` be used?
|
|
348
|
+
3. Can `RefCell` / `Mutex` be used?
|
|
349
|
+
4. Can the data structure be split?
|
|
350
|
+
5. Do you really need FFI, SIMD, or low-level memory operations?
|
|
352
351
|
|
|
353
|
-
###
|
|
354
|
-
|
|
355
|
-
`unsafe` 不是效能開關,是你接手編譯器無法保證的安全責任。
|
|
356
|
-
|
|
357
|
-
---
|
|
352
|
+
### Principle
|
|
358
353
|
|
|
354
|
+
`unsafe` is not a performance switch; it is taking over the safety responsibilities that the compiler can no longer guarantee.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Naming Conventions
|
|
6
6
|
|
|
7
|
-
|
|
|
7
|
+
| Type | Best Practice |
|
|
8
8
|
|---|---|
|
|
9
9
|
| struct | `UserProfile` |
|
|
10
10
|
| enum | `PaymentStatus` |
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## Getter
|
|
22
|
+
## Getter Naming
|
|
23
23
|
|
|
24
|
-
Rust
|
|
24
|
+
Rust convention typically does not include the `get_` prefix.
|
|
25
25
|
|
|
26
|
-
###
|
|
26
|
+
### Bad
|
|
27
27
|
|
|
28
28
|
```rust
|
|
29
29
|
impl User {
|
|
@@ -33,7 +33,7 @@ impl User {
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
###
|
|
36
|
+
### Good
|
|
37
37
|
|
|
38
38
|
```rust
|
|
39
39
|
impl User {
|
|
@@ -49,15 +49,15 @@ impl User {
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
-
## Conversion
|
|
52
|
+
## Conversion Naming: `as_`, `to_`, `into_`
|
|
53
53
|
|
|
54
|
-
|
|
|
54
|
+
| Naming | Meaning | Example |
|
|
55
55
|
|---|---|---|
|
|
56
|
-
| `as_` |
|
|
57
|
-
| `to_` |
|
|
58
|
-
| `into_` |
|
|
56
|
+
| `as_` | Cheap borrowing conversion | `as_str()` |
|
|
57
|
+
| `to_` | Produces a new value, may allocate memory | `to_string()` |
|
|
58
|
+
| `into_` | Consumes self, converts to another owned value | `into_bytes()` |
|
|
59
59
|
|
|
60
|
-
###
|
|
60
|
+
### Example
|
|
61
61
|
|
|
62
62
|
```rust
|
|
63
63
|
struct UserName(String);
|
|
@@ -75,9 +75,9 @@ impl UserName {
|
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
-
## Iterator
|
|
78
|
+
## Iterator Naming
|
|
79
79
|
|
|
80
|
-
Collection
|
|
80
|
+
For Collection types, it is recommended to provide:
|
|
81
81
|
|
|
82
82
|
```rust
|
|
83
83
|
fn iter(&self) -> Iter
|
|
@@ -89,7 +89,7 @@ fn into_iter(self) -> IntoIter
|
|
|
89
89
|
|
|
90
90
|
## Module Style
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
Recommended structure:
|
|
93
93
|
|
|
94
94
|
```text
|
|
95
95
|
src/
|
|
@@ -110,22 +110,22 @@ src/
|
|
|
110
110
|
user_service.rs
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
###
|
|
113
|
+
### Principles
|
|
114
114
|
|
|
115
|
-
|
|
|
115
|
+
| Principle | Description |
|
|
116
116
|
|---|---|
|
|
117
|
-
| `main.rs`
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
| `pub`
|
|
121
|
-
| error
|
|
122
|
-
|
|
|
117
|
+
| Thin `main.rs` | Only responsible for assembly and startup |
|
|
118
|
+
| Business logic in `lib.rs` | Facilitates testing |
|
|
119
|
+
| Domain types don't depend on infra | Avoids coupling |
|
|
120
|
+
| Minimize `pub` | Private by default |
|
|
121
|
+
| Centralized error management | Don't use `String` for errors everywhere |
|
|
122
|
+
| Tests near code | Small unit tests can be placed in the same file under `mod tests` |
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
126
|
## Import Style
|
|
127
127
|
|
|
128
|
-
###
|
|
128
|
+
### Bad
|
|
129
129
|
|
|
130
130
|
```rust
|
|
131
131
|
use crate::domain::user::User;
|
|
@@ -133,19 +133,19 @@ use crate::domain::user::UserId;
|
|
|
133
133
|
use crate::domain::user::UserStatus;
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
###
|
|
136
|
+
### Good
|
|
137
137
|
|
|
138
138
|
```rust
|
|
139
139
|
use crate::domain::user::{User, UserId, UserStatus};
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
###
|
|
142
|
+
### Not Recommended
|
|
143
143
|
|
|
144
144
|
```rust
|
|
145
145
|
use crate::domain::user::*;
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
`*`
|
|
148
|
+
The glob operator `*` is acceptable in tests or preludes, but should be avoided in formal modules.
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
@@ -153,7 +153,7 @@ use crate::domain::user::*;
|
|
|
153
153
|
|
|
154
154
|
### Application / CLI
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
Flexible errors can be used:
|
|
157
157
|
|
|
158
158
|
```rust
|
|
159
159
|
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
@@ -161,9 +161,9 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
161
161
|
}
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
### Library /
|
|
164
|
+
### Library / Domain
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
Explicit enums are recommended:
|
|
167
167
|
|
|
168
168
|
```rust
|
|
169
169
|
#[derive(Debug)]
|
|
@@ -175,7 +175,7 @@ pub enum CreateUserError {
|
|
|
175
175
|
|
|
176
176
|
### Service
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
Preserve error context:
|
|
179
179
|
|
|
180
180
|
```rust
|
|
181
181
|
fn create_user(email: &str) -> Result<(), CreateUserError> {
|
|
@@ -189,9 +189,9 @@ fn create_user(email: &str) -> Result<(), CreateUserError> {
|
|
|
189
189
|
|
|
190
190
|
---
|
|
191
191
|
|
|
192
|
-
# 5. Rust
|
|
192
|
+
# 5. Rust Toolchain Coding Standard
|
|
193
193
|
|
|
194
|
-
##
|
|
194
|
+
## Recommended Commands
|
|
195
195
|
|
|
196
196
|
```bash
|
|
197
197
|
cargo fmt -- --check
|
|
@@ -201,7 +201,7 @@ cargo test
|
|
|
201
201
|
cargo build --release
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
##
|
|
204
|
+
## Recommended CI
|
|
205
205
|
|
|
206
206
|
```yaml
|
|
207
207
|
name: Rust CI
|
|
@@ -232,35 +232,35 @@ jobs:
|
|
|
232
232
|
|
|
233
233
|
---
|
|
234
234
|
|
|
235
|
-
# 6.
|
|
235
|
+
# 6. Summary Table: Good vs. Bad Practices
|
|
236
236
|
|
|
237
|
-
|
|
|
237
|
+
| Topic | Good Practice | Bad Practice |
|
|
238
238
|
|---|---|---|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
| ID /
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
|
|
|
239
|
+
| Reading text | `&str` | `String` |
|
|
240
|
+
| Reading arrays | `&[T]` | `&Vec<T>` |
|
|
241
|
+
| Optional values | `Option<T>` | Empty string, `-1`, dummy data |
|
|
242
|
+
| Possible failure | `Result<T, E>` | `panic!`, `unwrap()` |
|
|
243
|
+
| Finite states | `enum` | `String`, multiple booleans |
|
|
244
|
+
| ID / Amount / Units | Newtype | Always using `u64` / `String` |
|
|
245
|
+
| Many parameters | Builder | Extremely long constructor |
|
|
246
|
+
| State transitions | Typestate | Runtime flags |
|
|
247
|
+
| Abstract behavior | trait | Hard-coding concrete dependencies |
|
|
248
|
+
| Data conversion | iterator chain | Large temporary mutable vectors |
|
|
249
|
+
| Resource management | RAII / `Drop` | Manual close (easy to forget) |
|
|
250
|
+
| Shared data | Clear ownership / channel | `Arc<Mutex<T>>` everywhere |
|
|
251
|
+
| Async concurrency | Bounded concurrency | Unrestricted `spawn` |
|
|
252
|
+
| Formatting | `cargo fmt` | Manual formatting |
|
|
253
|
+
| Static analysis | `cargo clippy` | Relying on manual review |
|
|
254
254
|
|
|
255
255
|
---
|
|
256
256
|
|
|
257
|
-
# 7.
|
|
257
|
+
# 7. Practical Rust Coding Guidelines
|
|
258
258
|
|
|
259
259
|
---
|
|
260
260
|
|
|
261
|
-
##
|
|
261
|
+
## When Writing Functions
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
Priority order:
|
|
264
264
|
|
|
265
265
|
```rust
|
|
266
266
|
fn read_only(value: &T)
|
|
@@ -273,7 +273,7 @@ fn flexible_owned(value: impl Into<T>)
|
|
|
273
273
|
|
|
274
274
|
---
|
|
275
275
|
|
|
276
|
-
##
|
|
276
|
+
## When Writing Structs
|
|
277
277
|
|
|
278
278
|
```rust
|
|
279
279
|
pub struct User {
|
|
@@ -298,7 +298,7 @@ impl User {
|
|
|
298
298
|
|
|
299
299
|
---
|
|
300
300
|
|
|
301
|
-
##
|
|
301
|
+
## When Writing Enums
|
|
302
302
|
|
|
303
303
|
```rust
|
|
304
304
|
enum Command {
|
|
@@ -308,7 +308,7 @@ enum Command {
|
|
|
308
308
|
}
|
|
309
309
|
```
|
|
310
310
|
|
|
311
|
-
|
|
311
|
+
Better than:
|
|
312
312
|
|
|
313
313
|
```rust
|
|
314
314
|
struct Command {
|
|
@@ -319,7 +319,7 @@ struct Command {
|
|
|
319
319
|
|
|
320
320
|
---
|
|
321
321
|
|
|
322
|
-
##
|
|
322
|
+
## When Writing Errors
|
|
323
323
|
|
|
324
324
|
```rust
|
|
325
325
|
#[derive(Debug)]
|
|
@@ -330,7 +330,7 @@ enum AppError {
|
|
|
330
330
|
}
|
|
331
331
|
```
|
|
332
332
|
|
|
333
|
-
|
|
333
|
+
Better than:
|
|
334
334
|
|
|
335
335
|
```rust
|
|
336
336
|
Err("something went wrong".to_string())
|
|
@@ -338,11 +338,11 @@ Err("something went wrong".to_string())
|
|
|
338
338
|
|
|
339
339
|
---
|
|
340
340
|
|
|
341
|
-
# 8.
|
|
341
|
+
# 8. Project Starter Recommendations
|
|
342
342
|
|
|
343
343
|
---
|
|
344
344
|
|
|
345
|
-
## CLI /
|
|
345
|
+
## CLI / Small Tools
|
|
346
346
|
|
|
347
347
|
```text
|
|
348
348
|
src/
|
|
@@ -352,7 +352,7 @@ src/
|
|
|
352
352
|
error.rs
|
|
353
353
|
```
|
|
354
354
|
|
|
355
|
-
|
|
355
|
+
Key points:
|
|
356
356
|
|
|
357
357
|
```rust
|
|
358
358
|
fn main() {
|
|
@@ -384,21 +384,21 @@ src/
|
|
|
384
384
|
repository.rs
|
|
385
385
|
```
|
|
386
386
|
|
|
387
|
-
|
|
387
|
+
Key points:
|
|
388
388
|
|
|
389
|
-
|
|
|
389
|
+
| Layer | Responsibility |
|
|
390
390
|
|---|---|
|
|
391
|
-
| `domain` |
|
|
392
|
-
| `service` |
|
|
393
|
-
| `infra` | DB
|
|
394
|
-
| `app` | DI /
|
|
395
|
-
| `main` |
|
|
391
|
+
| `domain` | Types, rules, states |
|
|
392
|
+
| `service` | Use cases |
|
|
393
|
+
| `infra` | DB, HTTP, Redis, Files |
|
|
394
|
+
| `app` | DI / Router / Runtime assembly |
|
|
395
|
+
| `main` | Startup |
|
|
396
396
|
|
|
397
397
|
---
|
|
398
398
|
|
|
399
|
-
# 9.
|
|
399
|
+
# 9. Key Conclusion
|
|
400
400
|
|
|
401
|
-
Rust
|
|
401
|
+
Good Rust code typically looks like this:
|
|
402
402
|
|
|
403
403
|
```rust
|
|
404
404
|
pub fn create_user(repo: &impl UserRepository, email: &str) -> Result<UserId, CreateUserError> {
|
|
@@ -412,56 +412,56 @@ pub fn create_user(repo: &impl UserRepository, email: &str) -> Result<UserId, Cr
|
|
|
412
412
|
}
|
|
413
413
|
```
|
|
414
414
|
|
|
415
|
-
|
|
415
|
+
It possesses several characteristics:
|
|
416
416
|
|
|
417
|
-
1.
|
|
418
|
-
2.
|
|
419
|
-
3.
|
|
420
|
-
4.
|
|
421
|
-
5.
|
|
422
|
-
6.
|
|
423
|
-
7.
|
|
424
|
-
8.
|
|
425
|
-
9.
|
|
426
|
-
10.
|
|
417
|
+
1. Inputs use borrows: `&str`
|
|
418
|
+
2. Invalid data is blocked by smart constructors: `Email::new`
|
|
419
|
+
3. Errors use `Result`
|
|
420
|
+
4. Business errors are categorizable: `CreateUserError`
|
|
421
|
+
5. Dependencies are abstracted via traits: `UserRepository`
|
|
422
|
+
6. No reckless `unwrap`
|
|
423
|
+
7. No stringly-typed states
|
|
424
|
+
8. No redundant clones
|
|
425
|
+
9. Easy to test
|
|
426
|
+
10. The compiler prevents a large class of errors
|
|
427
427
|
|
|
428
|
-
|
|
428
|
+
True Rust design patterns aren't about "making classes look pretty," but rather:
|
|
429
429
|
|
|
430
|
-
>
|
|
430
|
+
> Making illegal states unrepresentable, forcing error paths to be handled by the type system, and making ownership so clear it doesn't need to be guessed.
|
|
431
431
|
|
|
432
432
|
---
|
|
433
433
|
|
|
434
|
-
# 10.
|
|
434
|
+
# 10. References
|
|
435
435
|
|
|
436
|
-
- Rust Book - Understanding Ownership
|
|
436
|
+
- Rust Book - Understanding Ownership
|
|
437
437
|
https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
|
438
438
|
|
|
439
|
-
- Rust Book - Recoverable Errors with Result
|
|
439
|
+
- Rust Book - Recoverable Errors with Result
|
|
440
440
|
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
|
|
441
441
|
|
|
442
|
-
- Rust Book - Match Control Flow Construct
|
|
442
|
+
- Rust Book - Match Control Flow Construct
|
|
443
443
|
https://doc.rust-lang.org/book/ch06-02-match.html
|
|
444
444
|
|
|
445
|
-
- Rust Book - Smart Pointers
|
|
445
|
+
- Rust Book - Smart Pointers
|
|
446
446
|
https://doc.rust-lang.org/book/ch15-00-smart-pointers.html
|
|
447
447
|
|
|
448
|
-
- Rust Book - Message Passing
|
|
448
|
+
- Rust Book - Message Passing
|
|
449
449
|
https://doc.rust-lang.org/book/ch16-02-message-passing.html
|
|
450
450
|
|
|
451
|
-
- Rust Book - Async and Await
|
|
451
|
+
- Rust Book - Async and Await
|
|
452
452
|
https://doc.rust-lang.org/book/ch17-01-futures-and-syntax.html
|
|
453
453
|
|
|
454
|
-
- Rust API Guidelines - Naming
|
|
454
|
+
- Rust API Guidelines - Naming
|
|
455
455
|
https://rust-lang.github.io/api-guidelines/naming.html
|
|
456
456
|
|
|
457
|
-
- Clippy Lints
|
|
457
|
+
- Clippy Lints
|
|
458
458
|
https://doc.rust-lang.org/stable/clippy/lints.html
|
|
459
459
|
|
|
460
|
-
- Cargo Check
|
|
460
|
+
- Cargo Check
|
|
461
461
|
https://doc.rust-lang.org/cargo/commands/cargo-check.html
|
|
462
462
|
|
|
463
|
-
- Tokio Tutorial - Channels
|
|
463
|
+
- Tokio Tutorial - Channels
|
|
464
464
|
https://tokio.rs/tokio/tutorial/channels
|
|
465
465
|
|
|
466
|
-
- Tokio Mutex Documentation
|
|
466
|
+
- Tokio Mutex Documentation
|
|
467
467
|
https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 1. Rust
|
|
1
|
+
# 1. Core Principles of Rust Design
|
|
2
2
|
|
|
3
|
-
Rust
|
|
3
|
+
Rust's core is not about simply porting OOP / GoF Design Patterns, but rather utilizing:
|
|
4
4
|
|
|
5
5
|
- ownership
|
|
6
6
|
- borrowing
|
|
@@ -13,32 +13,32 @@ Rust 的核心不是把 OOP / GoF Design Patterns 照搬過來,而是用:
|
|
|
13
13
|
- RAII / `Drop`
|
|
14
14
|
- type system
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
to shift as many errors as possible to compile-time.
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Decision Matrix
|
|
19
19
|
|
|
20
|
-
|
|
|
20
|
+
| Question | Rust Best Practice |
|
|
21
21
|
|---|---|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
22
|
+
| Who owns this value? | Use ownership to express lifetime |
|
|
23
|
+
| Only reading? | Pass `&T`, `&str`, `&[T]` |
|
|
24
|
+
| Need to modify? | Pass `&mut T` |
|
|
25
|
+
| Might fail? | Return `Result<T, E>` |
|
|
26
|
+
| Might be empty? | Return `Option<T>` |
|
|
27
|
+
| Finite states? | Use `enum` |
|
|
28
|
+
| Abstract behavior? | Use `trait` |
|
|
29
|
+
| Illegal state? | Use the type system to prevent it |
|
|
30
|
+
| Need resource cleanup? | Use RAII / `Drop` |
|
|
31
|
+
| Shared state? | Prioritize clear ownership boundaries, then consider `Arc<Mutex<T>>` |
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
-
# 2. Design
|
|
35
|
+
# 2. Design Patterns: Best Practices
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
## Pattern 1
|
|
39
|
+
## Pattern 1: Borrowing-first API
|
|
40
40
|
|
|
41
|
-
###
|
|
41
|
+
### Bad Practice
|
|
42
42
|
|
|
43
43
|
```rust
|
|
44
44
|
fn print_name(name: String) {
|
|
@@ -49,14 +49,14 @@ fn main() {
|
|
|
49
49
|
let name = String::from("Alice");
|
|
50
50
|
print_name(name);
|
|
51
51
|
|
|
52
|
-
// name
|
|
52
|
+
// name has been moved and can no longer be used
|
|
53
53
|
// println!("{name}");
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Problem: Consumes ownership when only reading is required.
|
|
58
58
|
|
|
59
|
-
###
|
|
59
|
+
### Best Practice
|
|
60
60
|
|
|
61
61
|
```rust
|
|
62
62
|
fn print_name(name: &str) {
|
|
@@ -73,15 +73,15 @@ fn main() {
|
|
|
73
73
|
}
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
###
|
|
76
|
+
### Principle
|
|
77
77
|
|
|
78
|
-
`String`
|
|
78
|
+
`String` owns the data; `&str` is a borrowed string slice. Prefer `&str` when only reading text.
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
82
|
-
## Pattern 2
|
|
82
|
+
## Pattern 2: Slice API
|
|
83
83
|
|
|
84
|
-
###
|
|
84
|
+
### Bad Practice
|
|
85
85
|
|
|
86
86
|
```rust
|
|
87
87
|
fn sum(numbers: &Vec<i32>) -> i32 {
|
|
@@ -89,9 +89,9 @@ fn sum(numbers: &Vec<i32>) -> i32 {
|
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
Problem: Restricts the caller to pass a `Vec<i32>`.
|
|
93
93
|
|
|
94
|
-
###
|
|
94
|
+
### Best Practice
|
|
95
95
|
|
|
96
96
|
```rust
|
|
97
97
|
fn sum(numbers: &[i32]) -> i32 {
|
|
@@ -107,15 +107,15 @@ fn main() {
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
###
|
|
110
|
+
### Principle
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
When only contiguous data is needed, use `&[T]` instead of binding to `Vec<T>`.
|
|
113
113
|
|
|
114
114
|
---
|
|
115
115
|
|
|
116
|
-
## Pattern 3
|
|
116
|
+
## Pattern 3: `Option<T>` for "Possible Absence"
|
|
117
117
|
|
|
118
|
-
###
|
|
118
|
+
### Bad Practice
|
|
119
119
|
|
|
120
120
|
```rust
|
|
121
121
|
fn find_user_name(id: u64) -> String {
|
|
@@ -127,9 +127,9 @@ fn find_user_name(id: u64) -> String {
|
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
Problem: Is an empty string "missing data" or "the name is actually empty"?
|
|
131
131
|
|
|
132
|
-
###
|
|
132
|
+
### Best Practice
|
|
133
133
|
|
|
134
134
|
```rust
|
|
135
135
|
fn find_user_name(id: u64) -> Option<String> {
|
|
@@ -148,15 +148,15 @@ fn main() {
|
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
###
|
|
151
|
+
### Principle
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
Use `Option<T>` for "no value." Avoid using `null`, empty strings, or `-1` as magic values.
|
|
154
154
|
|
|
155
155
|
---
|
|
156
156
|
|
|
157
|
-
## Pattern 4
|
|
157
|
+
## Pattern 4: `Result<T, E>` for "Possible Failure"
|
|
158
158
|
|
|
159
|
-
###
|
|
159
|
+
### Bad Practice
|
|
160
160
|
|
|
161
161
|
```rust
|
|
162
162
|
use std::fs;
|
|
@@ -166,9 +166,9 @@ fn read_config() -> String {
|
|
|
166
166
|
}
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
Problem: `unwrap()` will cause the program to panic in production.
|
|
170
170
|
|
|
171
|
-
###
|
|
171
|
+
### Best Practice
|
|
172
172
|
|
|
173
173
|
```rust
|
|
174
174
|
use std::fs;
|
|
@@ -187,15 +187,15 @@ fn main() {
|
|
|
187
187
|
}
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
###
|
|
190
|
+
### Principle
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
Avoid reckless `unwrap()` in core logic (libraries, services, CLIs). Return `Result` when errors can occur.
|
|
193
193
|
|
|
194
194
|
---
|
|
195
195
|
|
|
196
|
-
## Pattern 5
|
|
196
|
+
## Pattern 5: Enum + Exhaustive Match
|
|
197
197
|
|
|
198
|
-
###
|
|
198
|
+
### Bad Practice
|
|
199
199
|
|
|
200
200
|
```rust
|
|
201
201
|
struct Payment {
|
|
@@ -207,9 +207,9 @@ fn can_refund(payment: &Payment) -> bool {
|
|
|
207
207
|
}
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
Problem: Values like `"paied"`, `"PAID"`, or `"unknown"` could creep in.
|
|
211
211
|
|
|
212
|
-
###
|
|
212
|
+
### Best Practice
|
|
213
213
|
|
|
214
214
|
```rust
|
|
215
215
|
enum PaymentStatus {
|
|
@@ -233,17 +233,17 @@ fn can_refund(payment: &Payment) -> bool {
|
|
|
233
233
|
}
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
-
###
|
|
236
|
+
### Principle
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
Use `enum` instead of `String` for finite states.
|
|
239
239
|
|
|
240
240
|
---
|
|
241
241
|
|
|
242
|
-
## Pattern 6
|
|
242
|
+
## Pattern 6: Newtype Pattern
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
Purpose: Prevent mixing up different domain values with the same underlying type.
|
|
245
245
|
|
|
246
|
-
###
|
|
246
|
+
### Bad Practice
|
|
247
247
|
|
|
248
248
|
```rust
|
|
249
249
|
fn load_user(user_id: u64) {}
|
|
@@ -251,11 +251,11 @@ fn load_order(order_id: u64) {}
|
|
|
251
251
|
|
|
252
252
|
fn main() {
|
|
253
253
|
let user_id = 100;
|
|
254
|
-
load_order(user_id); //
|
|
254
|
+
load_order(user_id); // Compiles, but semantically incorrect
|
|
255
255
|
}
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
-
###
|
|
258
|
+
### Best Practice
|
|
259
259
|
|
|
260
260
|
```rust
|
|
261
261
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
@@ -271,21 +271,21 @@ fn main() {
|
|
|
271
271
|
let user_id = UserId(100);
|
|
272
272
|
|
|
273
273
|
load_user(user_id);
|
|
274
|
-
// load_order(user_id); //
|
|
274
|
+
// load_order(user_id); // Compile error, prevents semantic mistakes
|
|
275
275
|
}
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
-
###
|
|
278
|
+
### Principle
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
Wrap IDs, amounts, units, permissions, and states in Newtypes.
|
|
281
281
|
|
|
282
282
|
---
|
|
283
283
|
|
|
284
|
-
## Pattern 7
|
|
284
|
+
## Pattern 7: Smart Constructor
|
|
285
285
|
|
|
286
|
-
|
|
286
|
+
Purpose: Ensure invariants when creating objects.
|
|
287
287
|
|
|
288
|
-
###
|
|
288
|
+
### Bad Practice
|
|
289
289
|
|
|
290
290
|
```rust
|
|
291
291
|
struct Email(String);
|
|
@@ -295,7 +295,7 @@ fn main() {
|
|
|
295
295
|
}
|
|
296
296
|
```
|
|
297
297
|
|
|
298
|
-
###
|
|
298
|
+
### Best Practice
|
|
299
299
|
|
|
300
300
|
```rust
|
|
301
301
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
@@ -325,17 +325,17 @@ fn main() -> Result<(), String> {
|
|
|
325
325
|
}
|
|
326
326
|
```
|
|
327
327
|
|
|
328
|
-
###
|
|
328
|
+
### Principle
|
|
329
329
|
|
|
330
|
-
|
|
330
|
+
Do not allow illegal objects to be constructed haphazardly. Keep fields private and provide a constructor.
|
|
331
331
|
|
|
332
332
|
---
|
|
333
333
|
|
|
334
|
-
## Pattern 8
|
|
334
|
+
## Pattern 8: Builder Pattern
|
|
335
335
|
|
|
336
|
-
|
|
336
|
+
Suitable for objects with many parameters or optional settings.
|
|
337
337
|
|
|
338
|
-
###
|
|
338
|
+
### Bad Practice
|
|
339
339
|
|
|
340
340
|
```rust
|
|
341
341
|
struct ServerConfig {
|
|
@@ -363,9 +363,9 @@ fn new_config(
|
|
|
363
363
|
}
|
|
364
364
|
```
|
|
365
365
|
|
|
366
|
-
|
|
366
|
+
Problem: Parameter order is error-prone, and extension is difficult.
|
|
367
367
|
|
|
368
|
-
###
|
|
368
|
+
### Best Practice
|
|
369
369
|
|
|
370
370
|
```rust
|
|
371
371
|
#[derive(Debug)]
|
|
@@ -435,11 +435,11 @@ fn main() {
|
|
|
435
435
|
|
|
436
436
|
---
|
|
437
437
|
|
|
438
|
-
## Pattern 9
|
|
438
|
+
## Pattern 9: Typestate Pattern
|
|
439
439
|
|
|
440
|
-
|
|
440
|
+
Purpose: Incorporate state flows into the type system, making invalid state transitions uncompilable.
|
|
441
441
|
|
|
442
|
-
###
|
|
442
|
+
### Bad Practice
|
|
443
443
|
|
|
444
444
|
```rust
|
|
445
445
|
struct Connection {
|
|
@@ -457,7 +457,7 @@ impl Connection {
|
|
|
457
457
|
}
|
|
458
458
|
```
|
|
459
459
|
|
|
460
|
-
###
|
|
460
|
+
### Best Practice
|
|
461
461
|
|
|
462
462
|
```rust
|
|
463
463
|
struct Disconnected;
|
|
@@ -500,22 +500,22 @@ fn main() {
|
|
|
500
500
|
}
|
|
501
501
|
```
|
|
502
502
|
|
|
503
|
-
###
|
|
503
|
+
### Principle
|
|
504
504
|
|
|
505
|
-
|
|
505
|
+
When state flows are fixed, Typestate can cause incorrect usage to fail at compile time.
|
|
506
506
|
|
|
507
507
|
---
|
|
508
508
|
|
|
509
|
-
## Pattern 10
|
|
509
|
+
## Pattern 10: Trait Abstraction
|
|
510
510
|
|
|
511
|
-
Rust
|
|
511
|
+
Polymorphism in Rust primarily relies on traits.
|
|
512
512
|
|
|
513
|
-
|
|
|
513
|
+
| Form | Syntax | Best For |
|
|
514
514
|
|---|---|---|
|
|
515
|
-
| Static dispatch | `T: Trait` |
|
|
516
|
-
| Dynamic dispatch | `dyn Trait` | runtime
|
|
515
|
+
| Static dispatch | `T: Trait` | Type decided at compile-time; better performance |
|
|
516
|
+
| Dynamic dispatch | `dyn Trait` | Implementation type decided at runtime |
|
|
517
517
|
|
|
518
|
-
### Static
|
|
518
|
+
### Static Dispatch
|
|
519
519
|
|
|
520
520
|
```rust
|
|
521
521
|
trait Repository {
|
|
@@ -535,7 +535,7 @@ fn create_user<R: Repository>(repo: &R, name: &str) {
|
|
|
535
535
|
}
|
|
536
536
|
```
|
|
537
537
|
|
|
538
|
-
### Dynamic
|
|
538
|
+
### Dynamic Dispatch
|
|
539
539
|
|
|
540
540
|
```rust
|
|
541
541
|
trait Repository {
|
|
@@ -555,15 +555,15 @@ fn create_user(repo: &dyn Repository, name: &str) {
|
|
|
555
555
|
}
|
|
556
556
|
```
|
|
557
557
|
|
|
558
|
-
###
|
|
558
|
+
### Recommendation
|
|
559
559
|
|
|
560
|
-
|
|
560
|
+
Default to generic `T: Trait`. Use `Box<dyn Trait>` / `&dyn Trait` only when you need to put different implementations into the same collection or when runtime injection is required.
|
|
561
561
|
|
|
562
562
|
---
|
|
563
563
|
|
|
564
|
-
## Pattern 11
|
|
564
|
+
## Pattern 11: Iterator Pattern
|
|
565
565
|
|
|
566
|
-
###
|
|
566
|
+
### Bad Practice
|
|
567
567
|
|
|
568
568
|
```rust
|
|
569
569
|
fn active_names(users: Vec<User>) -> Vec<String> {
|
|
@@ -579,7 +579,7 @@ fn active_names(users: Vec<User>) -> Vec<String> {
|
|
|
579
579
|
}
|
|
580
580
|
```
|
|
581
581
|
|
|
582
|
-
###
|
|
582
|
+
### Best Practice
|
|
583
583
|
|
|
584
584
|
```rust
|
|
585
585
|
struct User {
|
|
@@ -596,16 +596,15 @@ fn active_names(users: Vec<User>) -> Vec<String> {
|
|
|
596
596
|
}
|
|
597
597
|
```
|
|
598
598
|
|
|
599
|
-
###
|
|
599
|
+
### Principle
|
|
600
600
|
|
|
601
|
-
|
|
602
|
-
但不要為了炫技把簡單邏輯寫到難懂。
|
|
601
|
+
Data transformation flows are well-suited for iterator chains. However, do not over-engineer simple logic to the point of being unreadable.
|
|
603
602
|
|
|
604
603
|
---
|
|
605
604
|
|
|
606
|
-
## Pattern 12
|
|
605
|
+
## Pattern 12: RAII / Drop Guard
|
|
607
606
|
|
|
608
|
-
|
|
607
|
+
Purpose: DB transactions, file locks, mutex guards, temp files, resource cleanup.
|
|
609
608
|
|
|
610
609
|
```rust
|
|
611
610
|
struct Transaction {
|
|
@@ -635,31 +634,30 @@ impl Drop for Transaction {
|
|
|
635
634
|
fn main() {
|
|
636
635
|
let tx = Transaction::new();
|
|
637
636
|
|
|
638
|
-
//
|
|
637
|
+
// If a return or panic occurs midway, Drop will trigger a rollback
|
|
639
638
|
tx.commit();
|
|
640
639
|
}
|
|
641
640
|
```
|
|
642
641
|
|
|
643
|
-
###
|
|
642
|
+
### Principle
|
|
644
643
|
|
|
645
|
-
|
|
646
|
-
值離開 scope 時,由 `Drop` 負責清理。
|
|
644
|
+
Resource acquisition and release are bound to object lifetime. Cleanup is handled by `Drop` when a value leaves scope.
|
|
647
645
|
|
|
648
646
|
---
|
|
649
647
|
|
|
650
|
-
## Pattern 13
|
|
648
|
+
## Pattern 13: Choosing Smart Pointers
|
|
651
649
|
|
|
652
|
-
|
|
|
650
|
+
| Type | Purpose |
|
|
653
651
|
|---|---|
|
|
654
|
-
| `Box<T>` |
|
|
655
|
-
| `Rc<T>` |
|
|
656
|
-
| `Arc<T>` |
|
|
657
|
-
| `RefCell<T>` |
|
|
658
|
-
| `Mutex<T>` |
|
|
659
|
-
| `RwLock<T>` |
|
|
660
|
-
| `Cow<'a, T>` |
|
|
652
|
+
| `Box<T>` | Heap allocation, recursive types, trait object ownership |
|
|
653
|
+
| `Rc<T>` | Single-threaded shared ownership |
|
|
654
|
+
| `Arc<T>` | Multi-threaded shared ownership |
|
|
655
|
+
| `RefCell<T>` | Single-threaded interior mutability, runtime borrow check |
|
|
656
|
+
| `Mutex<T>` | Multi-threaded mutable shared data |
|
|
657
|
+
| `RwLock<T>` | Multiple readers, single writer |
|
|
658
|
+
| `Cow<'a, T>` | Clone-on-write; can be either borrowed or owned |
|
|
661
659
|
|
|
662
|
-
###
|
|
660
|
+
### Typical Example
|
|
663
661
|
|
|
664
662
|
```rust
|
|
665
663
|
use std::sync::{Arc, Mutex};
|
|
@@ -687,21 +685,21 @@ fn main() {
|
|
|
687
685
|
}
|
|
688
686
|
```
|
|
689
687
|
|
|
690
|
-
###
|
|
688
|
+
### Note
|
|
691
689
|
|
|
692
|
-
`Arc<Mutex<T>>`
|
|
690
|
+
`Arc<Mutex<T>>` is common but not the default answer. Using channels or an actor pattern to split ownership is often clearer.
|
|
693
691
|
|
|
694
692
|
---
|
|
695
693
|
|
|
696
|
-
## Pattern 14
|
|
694
|
+
## Pattern 14: Message Passing / Actor-like Pattern
|
|
697
695
|
|
|
698
|
-
|
|
696
|
+
Suitable for:
|
|
699
697
|
|
|
700
|
-
-
|
|
701
|
-
-
|
|
702
|
-
-
|
|
703
|
-
-
|
|
704
|
-
-
|
|
698
|
+
- Queue workers
|
|
699
|
+
- Background tasks
|
|
700
|
+
- Pipelines
|
|
701
|
+
- Command processors
|
|
702
|
+
- Proxy servers
|
|
705
703
|
|
|
706
704
|
```rust
|
|
707
705
|
use std::sync::mpsc;
|
|
@@ -735,12 +733,12 @@ fn main() {
|
|
|
735
733
|
|
|
736
734
|
---
|
|
737
735
|
|
|
738
|
-
## Pattern 15
|
|
736
|
+
## Pattern 15: Async Bounded Concurrency
|
|
739
737
|
|
|
740
|
-
###
|
|
738
|
+
### Bad Practice
|
|
741
739
|
|
|
742
740
|
```rust
|
|
743
|
-
//
|
|
741
|
+
// Conceptual illustration: Spawning tasks without limits
|
|
744
742
|
for item in items {
|
|
745
743
|
tokio::spawn(async move {
|
|
746
744
|
process(item).await;
|
|
@@ -748,9 +746,9 @@ for item in items {
|
|
|
748
746
|
}
|
|
749
747
|
```
|
|
750
748
|
|
|
751
|
-
|
|
749
|
+
Problem: Can overwhelm CPU, memory, DB connections, or API rate limits.
|
|
752
750
|
|
|
753
|
-
###
|
|
751
|
+
### Best Practice
|
|
754
752
|
|
|
755
753
|
```rust
|
|
756
754
|
use std::sync::Arc;
|
|
@@ -781,9 +779,6 @@ async fn main() {
|
|
|
781
779
|
}
|
|
782
780
|
```
|
|
783
781
|
|
|
784
|
-
###
|
|
785
|
-
|
|
786
|
-
Async 任務要有上限。不要無限制 `spawn`。
|
|
787
|
-
|
|
788
|
-
---
|
|
782
|
+
### Principle
|
|
789
783
|
|
|
784
|
+
Async tasks should have a cap. Do not `spawn` without limits.
|