@canonical/code-standards 0.1.0 → 0.1.1

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/data/rust.ttl CHANGED
@@ -20,16 +20,11 @@ cs:RustCategory a cs:Category ;
20
20
  cs:NewtypeWrappers a cs:CodeStandard ;
21
21
  cs:name "rust/types/newtype-wrappers" ;
22
22
  cs:hasCategory cs:RustCategory ;
23
- cs:description """Use the newtype pattern to create distinct types for domain-specific values, preventing accidental mixing of semantically different data. This Haskell-inspired pattern provides compile-time safety with zero runtime cost.
24
-
25
- **Ratings:**
26
- - Impact: A (prevents mixing semantically different values)
27
- - Feasibility: B (requires discipline; some boilerplate)
28
- - Idiomaticity: S (core Rust pattern, zero-cost abstraction)
29
- - FP Purity: A (Haskell-inspired; phantom types possible)""" ;
30
- cs:dos """
31
- (Do) Wrap primitive types in newtypes for domain semantics.
32
- ```rust
23
+ cs:description """Use the newtype pattern to create distinct types for domain-specific values, preventing accidental mixing of semantically different data. This Haskell-inspired pattern provides compile-time safety with zero runtime cost.""" ;
24
+ cs:do [
25
+ cs:description "Wrap primitive types in newtypes for domain semantics." ;
26
+ cs:language "rust" ;
27
+ cs:code """
33
28
  /// A validated package name following sem conventions
34
29
  #[derive(Debug, Clone, PartialEq, Eq, Hash)]
35
30
  pub struct PackageName(String);
@@ -50,10 +45,12 @@ impl PackageName {
50
45
  &self.0
51
46
  }
52
47
  }
53
- ```
54
-
55
- (Do) Use newtypes to distinguish semantically different IDs.
56
- ```rust
48
+ """
49
+ ] ;
50
+ cs:do [
51
+ cs:description "Use newtypes to distinguish semantically different IDs." ;
52
+ cs:language "rust" ;
53
+ cs:code """
57
54
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58
55
  pub struct UserId(u64);
59
56
 
@@ -65,17 +62,21 @@ fn process_order(user: UserId, order: OrderId) { /* ... */ }
65
62
 
66
63
  // This won't compile:
67
64
  // process_order(order_id, user_id); // Error: expected UserId, found OrderId
68
- ```
69
-
70
- (Do) Derive common traits to maintain ergonomics.
71
- ```rust
65
+ """
66
+ ] ;
67
+ cs:do [
68
+ cs:description "Derive common traits to maintain ergonomics." ;
69
+ cs:language "rust" ;
70
+ cs:code """
72
71
  #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
73
72
  #[serde(transparent)]
74
73
  pub struct Uri(String);
75
- ```
76
-
77
- (Do) Use phantom types for compile-time state tracking.
78
- ```rust
74
+ """
75
+ ] ;
76
+ cs:do [
77
+ cs:description "Use phantom types for compile-time state tracking." ;
78
+ cs:language "rust" ;
79
+ cs:code """
79
80
  use std::marker::PhantomData;
80
81
 
81
82
  struct Validated;
@@ -98,11 +99,12 @@ impl Input<Validated> {
98
99
  // Only validated inputs can be processed
99
100
  }
100
101
  }
101
- ```
102
- """ ;
103
- cs:donts """
104
- (Don't) Use raw primitive types for domain concepts.
105
- ```rust
102
+ """
103
+ ] ;
104
+ cs:dont [
105
+ cs:description "Use raw primitive types for domain concepts." ;
106
+ cs:language "rust" ;
107
+ cs:code """
106
108
  // Bad: Raw strings lose semantic meaning
107
109
  fn load_package(name: String, path: String, uri: String) {
108
110
  // Easy to mix up arguments - compiler can't help
@@ -110,10 +112,12 @@ fn load_package(name: String, path: String, uri: String) {
110
112
 
111
113
  // Bad: Easy to accidentally swap arguments
112
114
  load_package(uri, name, path); // Compiles but wrong!
113
- ```
114
-
115
- (Don't) Create newtypes without validation when invariants exist.
116
- ```rust
115
+ """
116
+ ] ;
117
+ cs:dont [
118
+ cs:description "Create newtypes without validation when invariants exist." ;
119
+ cs:language "rust" ;
120
+ cs:code """
117
121
  // Bad: Allows invalid state
118
122
  pub struct Email(pub String); // pub field allows any string
119
123
 
@@ -128,15 +132,17 @@ impl Email {
128
132
  }
129
133
  }
130
134
  }
131
- ```
132
-
133
- (Don't) Over-wrap types that don't need semantic distinction.
134
- ```rust
135
+ """
136
+ ] ;
137
+ cs:dont [
138
+ cs:description "Over-wrap types that don't need semantic distinction." ;
139
+ cs:language "rust" ;
140
+ cs:code """
135
141
  // Bad: Unnecessary wrapping of implementation details
136
142
  struct LoopCounter(usize); // Just use usize
137
143
  struct TempBuffer(Vec<u8>); // Just use Vec<u8>
138
- ```
139
- """ .
144
+ """
145
+ ] .
140
146
 
141
147
  # =============================================================================
142
148
  # Standard 2: Error Context Enrichment
@@ -145,16 +151,11 @@ struct TempBuffer(Vec<u8>); // Just use Vec<u8>
145
151
  cs:ErrorContextEnrichment a cs:CodeStandard ;
146
152
  cs:name "rust/errors/context-enrichment" ;
147
153
  cs:hasCategory cs:RustCategory ;
148
- cs:description """Always enrich errors with contextual information such as file paths, operation names, and relevant state. This dramatically improves debugging experience and follows the principle of errors as values.
149
-
150
- **Ratings:**
151
- - Impact: S (dramatically improves debugging experience)
152
- - Feasibility: A (thiserror + map_err pattern already established)
153
- - Idiomaticity: S (Rust community best practice)
154
- - FP Purity: B (error as value; monadic error propagation)""" ;
155
- cs:dos """
156
- (Do) Use thiserror to create structured, contextual error types.
157
- ```rust
154
+ cs:description """Always enrich errors with contextual information such as file paths, operation names, and relevant state. This dramatically improves debugging experience and follows the principle of errors as values.""" ;
155
+ cs:do [
156
+ cs:description "Use thiserror to create structured, contextual error types." ;
157
+ cs:language "rust" ;
158
+ cs:code """
158
159
  use thiserror::Error;
159
160
  use std::path::PathBuf;
160
161
 
@@ -182,10 +183,12 @@ pub enum GraphError {
182
183
  message: String,
183
184
  },
184
185
  }
185
- ```
186
-
187
- (Do) Use map_err to add context when propagating errors.
188
- ```rust
186
+ """
187
+ ] ;
188
+ cs:do [
189
+ cs:description "Use map_err to add context when propagating errors." ;
190
+ cs:language "rust" ;
191
+ cs:code """
189
192
  fn load_package(path: &Path) -> Result<Package, GraphError> {
190
193
  let content = fs::read_to_string(path)
191
194
  .map_err(|e| GraphError::FileRead {
@@ -201,10 +204,12 @@ fn load_package(path: &Path) -> Result<Package, GraphError> {
201
204
 
202
205
  Ok(Package::from_manifest(manifest))
203
206
  }
204
- ```
205
-
206
- (Do) Chain errors to preserve the full error trail.
207
- ```rust
207
+ """
208
+ ] ;
209
+ cs:do [
210
+ cs:description "Chain errors to preserve the full error trail." ;
211
+ cs:language "rust" ;
212
+ cs:code """
208
213
  #[derive(Error, Debug)]
209
214
  pub enum AppError {
210
215
  #[error("Failed to load configuration")]
@@ -220,10 +225,12 @@ pub enum AppError {
220
225
  source: GraphError,
221
226
  },
222
227
  }
223
- ```
224
-
225
- (Do) Include actionable information in error messages.
226
- ```rust
228
+ """
229
+ ] ;
230
+ cs:do [
231
+ cs:description "Include actionable information in error messages." ;
232
+ cs:language "rust" ;
233
+ cs:code """
227
234
  #[derive(Error, Debug)]
228
235
  pub enum ValidationError {
229
236
  #[error("Package name '{name}' is invalid: {reason}. Valid names contain only alphanumeric characters, hyphens, and underscores.")]
@@ -232,11 +239,12 @@ pub enum ValidationError {
232
239
  reason: &'static str,
233
240
  },
234
241
  }
235
- ```
236
- """ ;
237
- cs:donts """
238
- (Don't) Use generic string errors that lose context.
239
- ```rust
242
+ """
243
+ ] ;
244
+ cs:dont [
245
+ cs:description "Use generic string errors that lose context." ;
246
+ cs:language "rust" ;
247
+ cs:code """
240
248
  // Bad: Loses type information and context
241
249
  fn load_config(path: &Path) -> Result<Config, String> {
242
250
  let content = fs::read_to_string(path)
@@ -245,10 +253,12 @@ fn load_config(path: &Path) -> Result<Config, String> {
245
253
  toml::from_str(&content)
246
254
  .map_err(|e| e.to_string()) // Which file? What went wrong?
247
255
  }
248
- ```
249
-
250
- (Don't) Discard error information with unwrap or expect in library code.
251
- ```rust
256
+ """
257
+ ] ;
258
+ cs:dont [
259
+ cs:description "Discard error information with unwrap or expect in library code." ;
260
+ cs:language "rust" ;
261
+ cs:code """
252
262
  // Bad: Panics instead of propagating
253
263
  fn parse_uri(s: &str) -> Uri {
254
264
  Uri::parse(s).unwrap() // Crashes on invalid input!
@@ -256,19 +266,23 @@ fn parse_uri(s: &str) -> Uri {
256
266
 
257
267
  // Bad: expect without context
258
268
  let file = File::open(path).expect("failed"); // Which path?
259
- ```
260
-
261
- (Don't) Create error types without Display or Debug.
262
- ```rust
269
+ """
270
+ ] ;
271
+ cs:dont [
272
+ cs:description "Create error types without Display or Debug." ;
273
+ cs:language "rust" ;
274
+ cs:code """
263
275
  // Bad: Unusable error type
264
276
  pub struct MyError {
265
277
  code: i32,
266
278
  // No Display impl - can't print meaningful message
267
279
  }
268
- ```
269
-
270
- (Don't) Use anyhow/eyre in library code (ok for applications).
271
- ```rust
280
+ """
281
+ ] ;
282
+ cs:dont [
283
+ cs:description "Use anyhow/eyre in library code (ok for applications)." ;
284
+ cs:language "rust" ;
285
+ cs:code """
272
286
  // Bad in libraries: Erases type information
273
287
  pub fn process() -> anyhow::Result<()> {
274
288
  // Callers can't match on specific error variants
@@ -278,8 +292,8 @@ pub fn process() -> anyhow::Result<()> {
278
292
  pub fn process() -> Result<(), ProcessError> {
279
293
  // Callers can handle specific cases
280
294
  }
281
- ```
282
- """ .
295
+ """
296
+ ] .
283
297
 
284
298
  # =============================================================================
285
299
  # Standard 3: Result Chaining (Monadic Composition)
@@ -288,16 +302,11 @@ pub fn process() -> Result<(), ProcessError> {
288
302
  cs:ResultChaining a cs:CodeStandard ;
289
303
  cs:name "rust/composition/result-chaining" ;
290
304
  cs:hasCategory cs:RustCategory ;
291
- cs:description """Use monadic Result chaining with the ? operator and combinators like and_then, map, and or_else for clean, composable error handling. This is the Rust equivalent of Haskell's do-notation for the Either monad.
292
-
293
- **Ratings:**
294
- - Impact: A (cleaner control flow, eliminates nested matches)
295
- - Feasibility: S (already natural in Rust)
296
- - Idiomaticity: S (idiomatic Rust; ? operator is standard)
297
- - FP Purity: S (direct monadic composition / Kleisli arrows)""" ;
298
- cs:dos """
299
- (Do) Use the ? operator for clean Result propagation.
300
- ```rust
305
+ cs:description """Use monadic Result chaining with the ? operator and combinators like and_then, map, and or_else for clean, composable error handling. This is the Rust equivalent of Haskell's do-notation for the Either monad.""" ;
306
+ cs:do [
307
+ cs:description "Use the ? operator for clean Result propagation." ;
308
+ cs:language "rust" ;
309
+ cs:code """
301
310
  fn load_project_graph() -> Result<(SemStore, Vec<PackageGraph>), GraphError> {
302
311
  let project_root = find_project_root()
303
312
  .ok_or(GraphError::NoProjectRoot)?;
@@ -318,20 +327,24 @@ fn load_project_graph() -> Result<(SemStore, Vec<PackageGraph>), GraphError> {
318
327
 
319
328
  Ok((store, package_graphs))
320
329
  }
321
- ```
322
-
323
- (Do) Use and_then for dependent computations.
324
- ```rust
330
+ """
331
+ ] ;
332
+ cs:do [
333
+ cs:description "Use and_then for dependent computations." ;
334
+ cs:language "rust" ;
335
+ cs:code """
325
336
  fn process_user_input(input: &str) -> Result<Output, ProcessError> {
326
337
  parse_input(input)
327
338
  .and_then(|parsed| validate(parsed))
328
339
  .and_then(|valid| transform(valid))
329
340
  .and_then(|transformed| save(transformed))
330
341
  }
331
- ```
332
-
333
- (Do) Use map for infallible transformations within Result.
334
- ```rust
342
+ """
343
+ ] ;
344
+ cs:do [
345
+ cs:description "Use map for infallible transformations within Result." ;
346
+ cs:language "rust" ;
347
+ cs:code """
335
348
  fn get_package_names(path: &Path) -> Result<Vec<String>, IoError> {
336
349
  fs::read_dir(path)?
337
350
  .filter_map(|entry| entry.ok())
@@ -347,19 +360,23 @@ fn get_config_value(key: &str) -> Result<String, ConfigError> {
347
360
  .map(|v| v.to_string())
348
361
  .ok_or(ConfigError::MissingKey(key.to_string()))
349
362
  }
350
- ```
351
-
352
- (Do) Use or_else for fallback computations.
353
- ```rust
363
+ """
364
+ ] ;
365
+ cs:do [
366
+ cs:description "Use or_else for fallback computations." ;
367
+ cs:language "rust" ;
368
+ cs:code """
354
369
  fn find_config() -> Result<Config, ConfigError> {
355
370
  load_config_from_env()
356
371
  .or_else(|_| load_config_from_file())
357
372
  .or_else(|_| Ok(Config::default()))
358
373
  }
359
- ```
360
-
361
- (Do) Combine multiple Results with the ? operator in sequence.
362
- ```rust
374
+ """
375
+ ] ;
376
+ cs:do [
377
+ cs:description "Combine multiple Results with the ? operator in sequence." ;
378
+ cs:language "rust" ;
379
+ cs:code """
363
380
  fn setup_application() -> Result<App, SetupError> {
364
381
  let config = load_config()?;
365
382
  let db = connect_database(&config.db_url)?;
@@ -368,11 +385,12 @@ fn setup_application() -> Result<App, SetupError> {
368
385
 
369
386
  Ok(App { config, db, cache, logger })
370
387
  }
371
- ```
372
- """ ;
373
- cs:donts """
374
- (Don't) Use nested match expressions for Result handling.
375
- ```rust
388
+ """
389
+ ] ;
390
+ cs:dont [
391
+ cs:description "Use nested match expressions for Result handling." ;
392
+ cs:language "rust" ;
393
+ cs:code """
376
394
  // Bad: Deeply nested, hard to follow
377
395
  fn process(input: &str) -> Result<Output, Error> {
378
396
  match parse(input) {
@@ -390,35 +408,41 @@ fn process(input: &str) -> Result<Output, Error> {
390
408
  Err(e) => Err(e.into()),
391
409
  }
392
410
  }
393
- ```
394
-
395
- (Don't) Use unwrap() or expect() to bypass error handling.
396
- ```rust
411
+ """
412
+ ] ;
413
+ cs:dont [
414
+ cs:description "Use unwrap() or expect() to bypass error handling." ;
415
+ cs:language "rust" ;
416
+ cs:code """
397
417
  // Bad: Panics on error
398
418
  fn load_data() -> Data {
399
419
  let content = fs::read_to_string("data.json").unwrap();
400
420
  serde_json::from_str(&content).unwrap()
401
421
  }
402
- ```
403
-
404
- (Don't) Ignore errors silently.
405
- ```rust
422
+ """
423
+ ] ;
424
+ cs:dont [
425
+ cs:description "Ignore errors silently." ;
426
+ cs:language "rust" ;
427
+ cs:code """
406
428
  // Bad: Errors are silently dropped
407
429
  fn try_save(data: &Data) {
408
430
  let _ = fs::write("data.json", serde_json::to_string(data).unwrap_or_default());
409
431
  }
410
- ```
411
-
412
- (Don't) Convert all errors to strings early in the chain.
413
- ```rust
432
+ """
433
+ ] ;
434
+ cs:dont [
435
+ cs:description "Convert all errors to strings early in the chain." ;
436
+ cs:language "rust" ;
437
+ cs:code """
414
438
  // Bad: Loses error type information
415
439
  fn process() -> Result<(), String> {
416
440
  let x = step1().map_err(|e| e.to_string())?;
417
441
  let y = step2(x).map_err(|e| e.to_string())?; // Can't distinguish errors
418
442
  Ok(())
419
443
  }
420
- ```
421
- """ .
444
+ """
445
+ ] .
422
446
 
423
447
  # =============================================================================
424
448
  # Standard 4: Iterator Combinator Pipelines
@@ -427,16 +451,11 @@ fn process() -> Result<(), String> {
427
451
  cs:CombinatorPipelines a cs:CodeStandard ;
428
452
  cs:name "rust/iterators/combinator-pipelines" ;
429
453
  cs:hasCategory cs:RustCategory ;
430
- cs:description """Prefer iterator combinators (map, filter, flat_map, collect) over imperative loops for data transformations. This functional style is more declarative, composable, and often more performant due to lazy evaluation and compiler optimizations.
431
-
432
- **Ratings:**
433
- - Impact: A (more declarative, composable, often faster)
434
- - Feasibility: A (Rust iterators are excellent)
435
- - Idiomaticity: S (core Rust idiom)
436
- - FP Purity: S (direct FP; lazy evaluation, composable)""" ;
437
- cs:dos """
438
- (Do) Use iterator chains for data transformations.
439
- ```rust
454
+ cs:description """Prefer iterator combinators (map, filter, flat_map, collect) over imperative loops for data transformations. This functional style is more declarative, composable, and often more performant due to lazy evaluation and compiler optimizations.""" ;
455
+ cs:do [
456
+ cs:description "Use iterator chains for data transformations." ;
457
+ cs:language "rust" ;
458
+ cs:code """
440
459
  fn get_installed_packages() -> Result<Vec<String>, IoError> {
441
460
  let packages_path = sem_packages_dir()?;
442
461
 
@@ -457,28 +476,34 @@ fn get_installed_packages() -> Result<Vec<String>, IoError> {
457
476
  packages.sort();
458
477
  Ok(packages)
459
478
  }
460
- ```
461
-
462
- (Do) Use flat_map for one-to-many transformations.
463
- ```rust
479
+ """
480
+ ] ;
481
+ cs:do [
482
+ cs:description "Use flat_map for one-to-many transformations." ;
483
+ cs:language "rust" ;
484
+ cs:code """
464
485
  fn get_all_dependencies(packages: &[Package]) -> Vec<Dependency> {
465
486
  packages.iter()
466
487
  .flat_map(|pkg| pkg.dependencies())
467
488
  .collect()
468
489
  }
469
- ```
470
-
471
- (Do) Use filter_map to combine filter and map operations.
472
- ```rust
490
+ """
491
+ ] ;
492
+ cs:do [
493
+ cs:description "Use filter_map to combine filter and map operations." ;
494
+ cs:language "rust" ;
495
+ cs:code """
473
496
  fn parse_valid_numbers(strings: &[&str]) -> Vec<i32> {
474
497
  strings.iter()
475
498
  .filter_map(|s| s.parse::<i32>().ok())
476
499
  .collect()
477
500
  }
478
- ```
479
-
480
- (Do) Use fold/reduce for accumulating results.
481
- ```rust
501
+ """
502
+ ] ;
503
+ cs:do [
504
+ cs:description "Use fold/reduce for accumulating results." ;
505
+ cs:language "rust" ;
506
+ cs:code """
482
507
  fn total_size(files: &[PathBuf]) -> u64 {
483
508
  files.iter()
484
509
  .filter_map(|p| fs::metadata(p).ok())
@@ -490,10 +515,12 @@ fn merge_configs(configs: &[Config]) -> Config {
490
515
  configs.iter()
491
516
  .fold(Config::default(), |acc, cfg| acc.merge(cfg))
492
517
  }
493
- ```
494
-
495
- (Do) Chain multiple operations for complex transformations.
496
- ```rust
518
+ """
519
+ ] ;
520
+ cs:do [
521
+ cs:description "Chain multiple operations for complex transformations." ;
522
+ cs:language "rust" ;
523
+ cs:code """
497
524
  fn process_log_entries(entries: &[LogEntry]) -> HashMap<String, Vec<&LogEntry>> {
498
525
  entries.iter()
499
526
  .filter(|e| e.level >= LogLevel::Warning)
@@ -505,10 +532,12 @@ fn process_log_entries(entries: &[LogEntry]) -> HashMap<String, Vec<&LogEntry>>
505
532
  acc
506
533
  })
507
534
  }
508
- ```
509
-
510
- (Do) Use collect with turbofish for type-driven collection.
511
- ```rust
535
+ """
536
+ ] ;
537
+ cs:do [
538
+ cs:description "Use collect with turbofish for type-driven collection." ;
539
+ cs:language "rust" ;
540
+ cs:code """
512
541
  // Collect into different types based on need
513
542
  let vec: Vec<_> = iter.collect();
514
543
  let set: HashSet<_> = iter.collect();
@@ -518,11 +547,12 @@ let map: HashMap<_, _> = iter.map(|x| (x.id, x)).collect();
518
547
  let results: Result<Vec<_>, _> = items.iter()
519
548
  .map(|item| process(item))
520
549
  .collect();
521
- ```
522
- """ ;
523
- cs:donts """
524
- (Don't) Use imperative loops when combinators are clearer.
525
- ```rust
550
+ """
551
+ ] ;
552
+ cs:dont [
553
+ cs:description "Use imperative loops when combinators are clearer." ;
554
+ cs:language "rust" ;
555
+ cs:code """
526
556
  // Bad: Imperative style obscures intent
527
557
  fn get_names(users: &[User]) -> Vec<String> {
528
558
  let mut names = Vec::new();
@@ -541,10 +571,12 @@ fn get_names(users: &[User]) -> Vec<String> {
541
571
  .map(|u| u.name.clone())
542
572
  .collect()
543
573
  }
544
- ```
545
-
546
- (Don't) Collect intermediate results unnecessarily.
547
- ```rust
574
+ """
575
+ ] ;
576
+ cs:dont [
577
+ cs:description "Collect intermediate results unnecessarily." ;
578
+ cs:language "rust" ;
579
+ cs:code """
548
580
  // Bad: Unnecessary allocation
549
581
  let filtered: Vec<_> = items.iter().filter(|x| x.valid).collect();
550
582
  let mapped: Vec<_> = filtered.iter().map(|x| x.value).collect();
@@ -555,10 +587,12 @@ let result: i32 = items.iter()
555
587
  .filter(|x| x.valid)
556
588
  .map(|x| x.value)
557
589
  .sum();
558
- ```
559
-
560
- (Don't) Use for loops just to build up a Vec.
561
- ```rust
590
+ """
591
+ ] ;
592
+ cs:dont [
593
+ cs:description "Use for loops just to build up a Vec." ;
594
+ cs:language "rust" ;
595
+ cs:code """
562
596
  // Bad: Manual Vec building
563
597
  let mut results = Vec::new();
564
598
  for item in items {
@@ -567,10 +601,12 @@ for item in items {
567
601
 
568
602
  // Good: Use map and collect
569
603
  let results: Vec<_> = items.iter().map(transform).collect();
570
- ```
571
-
572
- (Don't) Nest loops when flat_map works.
573
- ```rust
604
+ """
605
+ ] ;
606
+ cs:dont [
607
+ cs:description "Nest loops when flat_map works." ;
608
+ cs:language "rust" ;
609
+ cs:code """
574
610
  // Bad: Nested loops
575
611
  let mut all_items = Vec::new();
576
612
  for container in containers {
@@ -583,8 +619,8 @@ for container in containers {
583
619
  let all_items: Vec<_> = containers.iter()
584
620
  .flat_map(|c| c.items())
585
621
  .collect();
586
- ```
587
- """ .
622
+ """
623
+ ] .
588
624
 
589
625
  # =============================================================================
590
626
  # Standard 5: Discriminated Unions (Sum Types)
@@ -593,16 +629,11 @@ let all_items: Vec<_> = containers.iter()
593
629
  cs:DiscriminatedUnions a cs:CodeStandard ;
594
630
  cs:name "rust/types/discriminated-unions" ;
595
631
  cs:hasCategory cs:RustCategory ;
596
- cs:description """Model state and variants with enums (algebraic sum types) rather than flags, inheritance, or stringly-typed values. Exhaustive pattern matching ensures all cases are handled at compile time - Rust's killer feature borrowed from ML/Haskell.
597
-
598
- **Ratings:**
599
- - Impact: S (exhaustive matching prevents bugs)
600
- - Feasibility: A (native Rust feature)
601
- - Idiomaticity: S (Rust's killer feature)
602
- - FP Purity: S (algebraic data types; Haskell-equivalent)""" ;
603
- cs:dos """
604
- (Do) Use enums to model mutually exclusive states.
605
- ```rust
632
+ cs:description """Model state and variants with enums (algebraic sum types) rather than flags, inheritance, or stringly-typed values. Exhaustive pattern matching ensures all cases are handled at compile time - Rust's killer feature borrowed from ML/Haskell.""" ;
633
+ cs:do [
634
+ cs:description "Use enums to model mutually exclusive states." ;
635
+ cs:language "rust" ;
636
+ cs:code """
606
637
  #[derive(Debug, Clone, PartialEq)]
607
638
  pub enum DepSpec {
608
639
  Workspace(String),
@@ -626,10 +657,12 @@ impl DepSpec {
626
657
  }
627
658
  }
628
659
  }
629
- ```
630
-
631
- (Do) Use enums for state machines with compile-time guarantees.
632
- ```rust
660
+ """
661
+ ] ;
662
+ cs:do [
663
+ cs:description "Use enums for state machines with compile-time guarantees." ;
664
+ cs:language "rust" ;
665
+ cs:code """
633
666
  enum ConnectionState {
634
667
  Disconnected,
635
668
  Connecting { attempt: u32 },
@@ -656,10 +689,12 @@ impl ConnectionState {
656
689
  }
657
690
  }
658
691
  }
659
- ```
660
-
661
- (Do) Use enums to make invalid states unrepresentable.
662
- ```rust
692
+ """
693
+ ] ;
694
+ cs:do [
695
+ cs:description "Use enums to make invalid states unrepresentable." ;
696
+ cs:language "rust" ;
697
+ cs:code """
663
698
  // User can be either anonymous or authenticated, never both
664
699
  enum User {
665
700
  Anonymous,
@@ -676,10 +711,12 @@ enum FieldState<T> {
676
711
  Touched { value: T, errors: Vec<ValidationError> },
677
712
  Submitted { value: T },
678
713
  }
679
- ```
680
-
681
- (Do) Leverage exhaustive matching for safety.
682
- ```rust
714
+ """
715
+ ] ;
716
+ cs:do [
717
+ cs:description "Leverage exhaustive matching for safety." ;
718
+ cs:language "rust" ;
719
+ cs:code """
683
720
  fn handle_result(result: QueryResult) -> Response {
684
721
  match result {
685
722
  QueryResult::Success(data) => Response::json(data),
@@ -691,11 +728,12 @@ fn handle_result(result: QueryResult) -> Response {
691
728
  // Compiler error if we forget a variant!
692
729
  }
693
730
  }
694
- ```
695
- """ ;
696
- cs:donts """
697
- (Don't) Use boolean flags for mutually exclusive states.
698
- ```rust
731
+ """
732
+ ] ;
733
+ cs:dont [
734
+ cs:description "Use boolean flags for mutually exclusive states." ;
735
+ cs:language "rust" ;
736
+ cs:code """
699
737
  // Bad: Multiple bools can have invalid combinations
700
738
  struct User {
701
739
  is_anonymous: bool,
@@ -707,10 +745,12 @@ struct User {
707
745
  struct Connection {
708
746
  state: String, // "connected", "disconnected", typos possible
709
747
  }
710
- ```
711
-
712
- (Don't) Use Option when you need more than two states.
713
- ```rust
748
+ """
749
+ ] ;
750
+ cs:dont [
751
+ cs:description "Use Option when you need more than two states." ;
752
+ cs:language "rust" ;
753
+ cs:code """
714
754
  // Bad: Option doesn't capture "loading" vs "error" vs "empty"
715
755
  struct DataView {
716
756
  data: Option<Vec<Item>>, // Is None loading, error, or empty?
@@ -723,10 +763,12 @@ enum DataState {
723
763
  Loaded(Vec<Item>),
724
764
  Error(String),
725
765
  }
726
- ```
727
-
728
- (Don't) Use inheritance-like patterns with trait objects when enums suffice.
729
- ```rust
766
+ """
767
+ ] ;
768
+ cs:dont [
769
+ cs:description "Use inheritance-like patterns with trait objects when enums suffice." ;
770
+ cs:language "rust" ;
771
+ cs:code """
730
772
  // Bad: Runtime dispatch when compile-time would work
731
773
  trait Shape {
732
774
  fn area(&self) -> f64;
@@ -748,10 +790,12 @@ impl Shape {
748
790
  }
749
791
  }
750
792
  }
751
- ```
752
-
753
- (Don't) Use integers or strings as type discriminators.
754
- ```rust
793
+ """
794
+ ] ;
795
+ cs:dont [
796
+ cs:description "Use integers or strings as type discriminators." ;
797
+ cs:language "rust" ;
798
+ cs:code """
755
799
  // Bad: Magic numbers
756
800
  const USER_TYPE_ADMIN: i32 = 1;
757
801
  const USER_TYPE_MEMBER: i32 = 2;
@@ -759,8 +803,8 @@ struct User { user_type: i32 }
759
803
 
760
804
  // Bad: Stringly typed
761
805
  struct Message { msg_type: String } // "request", "response", typos!
762
- ```
763
- """ .
806
+ """
807
+ ] .
764
808
 
765
809
  # =============================================================================
766
810
  # Standard 6: Kleisli Composition
@@ -769,16 +813,11 @@ struct Message { msg_type: String } // "request", "response", typos!
769
813
  cs:KleisliComposition a cs:CodeStandard ;
770
814
  cs:name "rust/functions/kleisli-composition" ;
771
815
  cs:hasCategory cs:RustCategory ;
772
- cs:description """Structure functions as `A -> Result<B, E>` (Kleisli arrows) for composable, chainable transformations. This enables powerful composition patterns where each step can fail, following the monadic composition style from Haskell.
773
-
774
- **Ratings:**
775
- - Impact: B (enables powerful composition patterns)
776
- - Feasibility: B (requires thinking in terms of monadic pipelines)
777
- - Idiomaticity: A (natural with ? and and_then)
778
- - FP Purity: S (direct Kleisli arrow pattern)""" ;
779
- cs:dos """
780
- (Do) Design functions as Kleisli arrows: `A -> Result<B, E>`.
781
- ```rust
816
+ cs:description """Structure functions as `A -> Result<B, E>` (Kleisli arrows) for composable, chainable transformations. This enables powerful composition patterns where each step can fail, following the monadic composition style from Haskell.""" ;
817
+ cs:do [
818
+ cs:description "Design functions as Kleisli arrows: `A -> Result<B, E>`." ;
819
+ cs:language "rust" ;
820
+ cs:code """
782
821
  // Each function is a Kleisli arrow that can be composed
783
822
  fn parse_config(input: &str) -> Result<RawConfig, ParseError> { /* ... */ }
784
823
  fn validate_config(raw: RawConfig) -> Result<ValidConfig, ValidationError> { /* ... */ }
@@ -801,10 +840,12 @@ fn process_input(s: &str) -> Result<Output, ProcessError> {
801
840
  .and_then(transform)
802
841
  .and_then(finalize)
803
842
  }
804
- ```
805
-
806
- (Do) Use combinators to compose fallible operations.
807
- ```rust
843
+ """
844
+ ] ;
845
+ cs:do [
846
+ cs:description "Use combinators to compose fallible operations." ;
847
+ cs:language "rust" ;
848
+ cs:code """
808
849
  impl DepSpec {
809
850
  pub fn from_value(value: &DependencyValue) -> Result<Self, ParseError> {
810
851
  match value {
@@ -821,10 +862,12 @@ impl DepSpec {
821
862
  }
822
863
  }
823
864
  }
824
- ```
825
-
826
- (Do) Create helper traits for method chaining when needed.
827
- ```rust
865
+ """
866
+ ] ;
867
+ cs:do [
868
+ cs:description "Create helper traits for method chaining when needed." ;
869
+ cs:language "rust" ;
870
+ cs:code """
828
871
  trait ResultExt<T, E> {
829
872
  fn and_try<U, F>(self, f: F) -> Result<U, E>
830
873
  where
@@ -845,10 +888,12 @@ let result = input
845
888
  .and_try(step1)
846
889
  .and_try(step2)
847
890
  .and_try(step3);
848
- ```
849
-
850
- (Do) Use the pipe pattern for readability.
851
- ```rust
891
+ """
892
+ ] ;
893
+ cs:do [
894
+ cs:description "Use the pipe pattern for readability." ;
895
+ cs:language "rust" ;
896
+ cs:code """
852
897
  // With a pipe trait or tap crate
853
898
  trait Pipe: Sized {
854
899
  fn pipe<F, R>(self, f: F) -> R where F: FnOnce(Self) -> R {
@@ -862,11 +907,12 @@ let result = input
862
907
  .pipe(parse)
863
908
  .and_then(|x| x.pipe(validate))
864
909
  .and_then(|x| x.pipe(transform));
865
- ```
866
- """ ;
867
- cs:donts """
868
- (Don't) Mix side effects into pure transformation chains.
869
- ```rust
910
+ """
911
+ ] ;
912
+ cs:dont [
913
+ cs:description "Mix side effects into pure transformation chains." ;
914
+ cs:language "rust" ;
915
+ cs:code """
870
916
  // Bad: Side effects hidden in chain
871
917
  fn process(input: &str) -> Result<Output, Error> {
872
918
  parse(input)
@@ -886,10 +932,12 @@ fn process(input: &str) -> Result<Output, Error> {
886
932
  let validated = validate(parsed)?;
887
933
  transform(validated)
888
934
  }
889
- ```
890
-
891
- (Don't) Break the chain with early returns when and_then works.
892
- ```rust
935
+ """
936
+ ] ;
937
+ cs:dont [
938
+ cs:description "Break the chain with early returns when and_then works." ;
939
+ cs:language "rust" ;
940
+ cs:code """
893
941
  // Bad: Breaks the monadic flow
894
942
  fn process(input: &str) -> Result<Output, Error> {
895
943
  let parsed = parse(input)?;
@@ -911,17 +959,19 @@ fn process(input: &str) -> Result<Output, Error> {
911
959
  .and_then(|t| (!t.is_empty()).then_some(t).ok_or(Error::Empty))
912
960
  .map(finalize)
913
961
  }
914
- ```
915
-
916
- (Don't) Use unwrap in the middle of a chain.
917
- ```rust
962
+ """
963
+ ] ;
964
+ cs:dont [
965
+ cs:description "Use unwrap in the middle of a chain." ;
966
+ cs:language "rust" ;
967
+ cs:code """
918
968
  // Bad: Panics break the monadic abstraction
919
969
  let result = items.iter()
920
970
  .map(|x| parse(x).unwrap()) // Panic!
921
971
  .filter(|x| x.is_valid())
922
972
  .collect();
923
- ```
924
- """ .
973
+ """
974
+ ] .
925
975
 
926
976
  # =============================================================================
927
977
  # Standard 7: Typestate Guards (Parse, Don't Validate)
@@ -930,16 +980,11 @@ let result = items.iter()
930
980
  cs:TypestateGuards a cs:CodeStandard ;
931
981
  cs:name "rust/validation/typestate-guards" ;
932
982
  cs:hasCategory cs:RustCategory ;
933
- cs:description """Use constructor validation to ensure only valid states can exist. Follow the 'parse, don't validate' principle: transform unvalidated data into validated types at system boundaries, making invalid states unrepresentable.
934
-
935
- **Ratings:**
936
- - Impact: S (invalid states become unrepresentable)
937
- - Feasibility: B (requires upfront design thinking)
938
- - Idiomaticity: A (recommended Rust pattern)
939
- - FP Purity: A (Haskell-inspired 'make illegal states unrepresentable')""" ;
940
- cs:dos """
941
- (Do) Validate in constructors to ensure type invariants.
942
- ```rust
983
+ cs:description """Use constructor validation to ensure only valid states can exist. Follow the 'parse, don't validate' principle: transform unvalidated data into validated types at system boundaries, making invalid states unrepresentable.""" ;
984
+ cs:do [
985
+ cs:description "Validate in constructors to ensure type invariants." ;
986
+ cs:language "rust" ;
987
+ cs:code """
943
988
  impl Manifest {
944
989
  pub fn from_path(path: &Path) -> Result<Self, ManifestError> {
945
990
  let content = fs::read_to_string(path)
@@ -962,10 +1007,12 @@ impl Manifest {
962
1007
  }
963
1008
  }
964
1009
  }
965
- ```
966
-
967
- (Do) Use the typestate pattern for compile-time state enforcement.
968
- ```rust
1010
+ """
1011
+ ] ;
1012
+ cs:do [
1013
+ cs:description "Use the typestate pattern for compile-time state enforcement." ;
1014
+ cs:language "rust" ;
1015
+ cs:code """
969
1016
  // States as zero-sized types
970
1017
  struct Draft;
971
1018
  struct Published;
@@ -1001,10 +1048,12 @@ impl Article<Published> {
1001
1048
  // Compile-time enforcement:
1002
1049
  // article_draft.view(); // Error: method not found
1003
1050
  // article_published.publish(); // Error: method not found
1004
- ```
1005
-
1006
- (Do) Parse into validated types at system boundaries.
1007
- ```rust
1051
+ """
1052
+ ] ;
1053
+ cs:do [
1054
+ cs:description "Parse into validated types at system boundaries." ;
1055
+ cs:language "rust" ;
1056
+ cs:code """
1008
1057
  // Raw input from external source
1009
1058
  struct RawUserInput {
1010
1059
  email: String,
@@ -1036,10 +1085,12 @@ impl Email {
1036
1085
  }
1037
1086
  }
1038
1087
  }
1039
- ```
1040
-
1041
- (Do) Make the validated state obvious in function signatures.
1042
- ```rust
1088
+ """
1089
+ ] ;
1090
+ cs:do [
1091
+ cs:description "Make the validated state obvious in function signatures." ;
1092
+ cs:language "rust" ;
1093
+ cs:code """
1043
1094
  // Functions that require validation communicate it via types
1044
1095
  fn send_email(to: &Email, subject: &str, body: &str) -> Result<(), SendError> {
1045
1096
  // Email is already validated - no need to check again
@@ -1048,11 +1099,12 @@ fn send_email(to: &Email, subject: &str, body: &str) -> Result<(), SendError> {
1048
1099
  fn create_account(user: ValidatedUser) -> Result<Account, AccountError> {
1049
1100
  // All fields are pre-validated
1050
1101
  }
1051
- ```
1052
- """ ;
1053
- cs:donts """
1054
- (Don't) Scatter validation logic throughout the codebase.
1055
- ```rust
1102
+ """
1103
+ ] ;
1104
+ cs:dont [
1105
+ cs:description "Scatter validation logic throughout the codebase." ;
1106
+ cs:language "rust" ;
1107
+ cs:code """
1056
1108
  // Bad: Validation repeated everywhere
1057
1109
  fn send_email(to: &str, subject: &str, body: &str) -> Result<(), Error> {
1058
1110
  if !to.contains('@') {
@@ -1067,10 +1119,12 @@ fn save_user(email: &str) -> Result<(), Error> {
1067
1119
  }
1068
1120
  // ... save logic
1069
1121
  }
1070
- ```
1071
-
1072
- (Don't) Allow construction of invalid objects.
1073
- ```rust
1122
+ """
1123
+ ] ;
1124
+ cs:dont [
1125
+ cs:description "Allow construction of invalid objects." ;
1126
+ cs:language "rust" ;
1127
+ cs:code """
1074
1128
  // Bad: Public fields allow invalid state
1075
1129
  pub struct Email {
1076
1130
  pub address: String, // Anyone can set invalid value
@@ -1082,10 +1136,12 @@ impl User {
1082
1136
  Self { email, age } // Could be invalid!
1083
1137
  }
1084
1138
  }
1085
- ```
1086
-
1087
- (Don't) Use validation functions that return bool.
1088
- ```rust
1139
+ """
1140
+ ] ;
1141
+ cs:dont [
1142
+ cs:description "Use validation functions that return bool." ;
1143
+ cs:language "rust" ;
1144
+ cs:code """
1089
1145
  // Bad: Caller can ignore the result
1090
1146
  fn is_valid_email(s: &str) -> bool {
1091
1147
  s.contains('@')
@@ -1099,10 +1155,12 @@ if is_valid_email(&email) {
1099
1155
  // Good: Parsing forces handling
1100
1156
  fn parse_email(s: &str) -> Result<Email, ValidationError>
1101
1157
  // Caller must handle the Result
1102
- ```
1103
-
1104
- (Don't) Re-validate already-validated data.
1105
- ```rust
1158
+ """
1159
+ ] ;
1160
+ cs:dont [
1161
+ cs:description "Re-validate already-validated data." ;
1162
+ cs:language "rust" ;
1163
+ cs:code """
1106
1164
  // Bad: Redundant validation
1107
1165
  fn process(user: ValidatedUser) -> Result<(), Error> {
1108
1166
  // ValidatedUser is already valid by construction!
@@ -1111,8 +1169,8 @@ fn process(user: ValidatedUser) -> Result<(), Error> {
1111
1169
  }
1112
1170
  // ...
1113
1171
  }
1114
- ```
1115
- """ .
1172
+ """
1173
+ ] .
1116
1174
 
1117
1175
  # =============================================================================
1118
1176
  # Standard 8: Small, Composable Traits
@@ -1121,16 +1179,11 @@ fn process(user: ValidatedUser) -> Result<(), Error> {
1121
1179
  cs:SmallComposableTraits a cs:CodeStandard ;
1122
1180
  cs:name "rust/traits/small-composable" ;
1123
1181
  cs:hasCategory cs:RustCategory ;
1124
- cs:description """Prefer small, focused traits that compose well over large monolithic interfaces. This Haskell typeclass-inspired approach enables better abstraction, easier testing, and more flexible code reuse.
1125
-
1126
- **Ratings:**
1127
- - Impact: A (better abstraction, easier testing)
1128
- - Feasibility: B (requires careful API design)
1129
- - Idiomaticity: S (Rust's trait system shines here)
1130
- - FP Purity: A (typeclass-inspired composition)""" ;
1131
- cs:dos """
1132
- (Do) Design small, single-purpose traits.
1133
- ```rust
1182
+ cs:description """Prefer small, focused traits that compose well over large monolithic interfaces. This Haskell typeclass-inspired approach enables better abstraction, easier testing, and more flexible code reuse.""" ;
1183
+ cs:do [
1184
+ cs:description "Design small, single-purpose traits." ;
1185
+ cs:language "rust" ;
1186
+ cs:code """
1134
1187
  /// Can be resolved from a prefixed form to a full URI
1135
1188
  trait Resolvable {
1136
1189
  fn resolve(&self, prefix_map: &PrefixMap) -> String;
@@ -1152,10 +1205,12 @@ trait Loadable: Sized {
1152
1205
  type Error;
1153
1206
  fn load(path: &Path) -> Result<Self, Self::Error>;
1154
1207
  }
1155
- ```
1156
-
1157
- (Do) Compose traits using supertraits and bounds.
1158
- ```rust
1208
+ """
1209
+ ] ;
1210
+ cs:do [
1211
+ cs:description "Compose traits using supertraits and bounds." ;
1212
+ cs:language "rust" ;
1213
+ cs:code """
1159
1214
  // Compose small traits into larger capabilities
1160
1215
  trait UriHandler: Resolvable + Compactable {}
1161
1216
 
@@ -1166,10 +1221,12 @@ impl<T: Resolvable + Compactable> UriHandler for T {}
1166
1221
  fn process_uri<T: Resolvable + Display>(uri: &T, map: &PrefixMap) -> String {
1167
1222
  format!("{} -> {}", uri, uri.resolve(map))
1168
1223
  }
1169
- ```
1170
-
1171
- (Do) Use extension traits to add methods to existing types.
1172
- ```rust
1224
+ """
1225
+ ] ;
1226
+ cs:do [
1227
+ cs:description "Use extension traits to add methods to existing types." ;
1228
+ cs:language "rust" ;
1229
+ cs:code """
1173
1230
  trait ResultExt<T, E> {
1174
1231
  fn context(self, msg: &str) -> Result<T, ContextError<E>>;
1175
1232
  fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T, ContextError<E>>;
@@ -1188,10 +1245,12 @@ impl<T, E> ResultExt<T, E> for Result<T, E> {
1188
1245
  // Usage
1189
1246
  let data = fs::read_to_string(path)
1190
1247
  .context("failed to read config")?;
1191
- ```
1192
-
1193
- (Do) Implement standard library traits for interoperability.
1194
- ```rust
1248
+ """
1249
+ ] ;
1250
+ cs:do [
1251
+ cs:description "Implement standard library traits for interoperability." ;
1252
+ cs:language "rust" ;
1253
+ cs:code """
1195
1254
  impl Default for SemStore {
1196
1255
  fn default() -> Self {
1197
1256
  Self::new().expect("Failed to create default SemStore")
@@ -1211,11 +1270,12 @@ impl FromStr for Version {
1211
1270
  // parsing logic
1212
1271
  }
1213
1272
  }
1214
- ```
1215
- """ ;
1216
- cs:donts """
1217
- (Don't) Create large, monolithic traits.
1218
- ```rust
1273
+ """
1274
+ ] ;
1275
+ cs:dont [
1276
+ cs:description "Create large, monolithic traits." ;
1277
+ cs:language "rust" ;
1278
+ cs:code """
1219
1279
  // Bad: Too many responsibilities
1220
1280
  trait Repository {
1221
1281
  fn connect(&mut self) -> Result<(), Error>;
@@ -1230,10 +1290,12 @@ trait Repository {
1230
1290
  fn execute_query(&self, query: &str) -> Result<QueryResult, Error>;
1231
1291
  // ... 20 more methods
1232
1292
  }
1233
- ```
1234
-
1235
- (Don't) Use trait objects when generics suffice.
1236
- ```rust
1293
+ """
1294
+ ] ;
1295
+ cs:dont [
1296
+ cs:description "Use trait objects when generics suffice." ;
1297
+ cs:language "rust" ;
1298
+ cs:code """
1237
1299
  // Bad: Unnecessary dynamic dispatch
1238
1300
  fn process(items: &[Box<dyn Processable>]) {
1239
1301
  for item in items {
@@ -1247,10 +1309,12 @@ fn process<T: Processable>(items: &[T]) {
1247
1309
  item.process();
1248
1310
  }
1249
1311
  }
1250
- ```
1251
-
1252
- (Don't) Require unused trait methods via blanket requirements.
1253
- ```rust
1312
+ """
1313
+ ] ;
1314
+ cs:dont [
1315
+ cs:description "Require unused trait methods via blanket requirements." ;
1316
+ cs:language "rust" ;
1317
+ cs:code """
1254
1318
  // Bad: Forces implementers to provide unused methods
1255
1319
  trait DataStore: Connect + Query + Mutate + Transaction + Cache + Log {
1256
1320
  // Most implementers don't need all of these
@@ -1260,10 +1324,12 @@ trait DataStore: Connect + Query + Mutate + Transaction + Cache + Log {
1260
1324
  fn process<T: Query + Mutate>(store: &mut T) {
1261
1325
  // Only requires the capabilities actually used
1262
1326
  }
1263
- ```
1264
-
1265
- (Don't) Use associated types when generic parameters work better.
1266
- ```rust
1327
+ """
1328
+ ] ;
1329
+ cs:dont [
1330
+ cs:description "Use associated types when generic parameters work better." ;
1331
+ cs:language "rust" ;
1332
+ cs:code """
1267
1333
  // Bad: Can only have one implementation per type
1268
1334
  trait Container {
1269
1335
  type Item;
@@ -1275,8 +1341,8 @@ trait Container<T> {
1275
1341
  fn get(&self) -> &T;
1276
1342
  }
1277
1343
  // Now Vec<i32> can be Container<i32> AND Container<String> if needed
1278
- ```
1279
- """ .
1344
+ """
1345
+ ] .
1280
1346
 
1281
1347
  # =============================================================================
1282
1348
  # Standard 9: Explicit Absence (Option Handling)
@@ -1285,16 +1351,11 @@ trait Container<T> {
1285
1351
  cs:ExplicitAbsence a cs:CodeStandard ;
1286
1352
  cs:name "rust/option/explicit-absence" ;
1287
1353
  cs:hasCategory cs:RustCategory ;
1288
- cs:description """Use Option explicitly and idiomatically; prefer Option<T> combinators (map, and_then, unwrap_or, ok_or) over null-like patterns. Option is Rust's Maybe monad - use it as such.
1289
-
1290
- **Ratings:**
1291
- - Impact: A (eliminates null-related bugs)
1292
- - Feasibility: S (Rust enforces this)
1293
- - Idiomaticity: S (fundamental Rust pattern)
1294
- - FP Purity: S (Maybe monad equivalent)""" ;
1295
- cs:dos """
1296
- (Do) Use Option combinators for clean transformations.
1297
- ```rust
1354
+ cs:description """Use Option explicitly and idiomatically; prefer Option<T> combinators (map, and_then, unwrap_or, ok_or) over null-like patterns. Option is Rust's Maybe monad - use it as such.""" ;
1355
+ cs:do [
1356
+ cs:description "Use Option combinators for clean transformations." ;
1357
+ cs:language "rust" ;
1358
+ cs:code """
1298
1359
  impl Manifest {
1299
1360
  pub fn display_name(&self) -> Option<String> {
1300
1361
  self.workspace.as_ref()
@@ -1310,10 +1371,12 @@ impl Manifest {
1310
1371
  self.workspace.as_ref().map(|w| w.members.clone())
1311
1372
  }
1312
1373
  }
1313
- ```
1314
-
1315
- (Do) Use unwrap_or and unwrap_or_else for defaults.
1316
- ```rust
1374
+ """
1375
+ ] ;
1376
+ cs:do [
1377
+ cs:description "Use unwrap_or and unwrap_or_else for defaults." ;
1378
+ cs:language "rust" ;
1379
+ cs:code """
1317
1380
  fn get_config_value(key: &str) -> String {
1318
1381
  config.get(key)
1319
1382
  .cloned()
@@ -1326,10 +1389,12 @@ fn get_timeout() -> Duration {
1326
1389
  .and_then(|s| s.parse().ok())
1327
1390
  .unwrap_or(Duration::from_secs(30))
1328
1391
  }
1329
- ```
1330
-
1331
- (Do) Use ok_or to convert Option to Result.
1332
- ```rust
1392
+ """
1393
+ ] ;
1394
+ cs:do [
1395
+ cs:description "Use ok_or to convert Option to Result." ;
1396
+ cs:language "rust" ;
1397
+ cs:code """
1333
1398
  fn find_package(name: &str) -> Result<&Package, PackageError> {
1334
1399
  packages.get(name)
1335
1400
  .ok_or_else(|| PackageError::NotFound(name.to_string()))
@@ -1339,10 +1404,12 @@ fn get_required_field<'a>(map: &'a HashMap<String, Value>, key: &str) -> Result<
1339
1404
  map.get(key)
1340
1405
  .ok_or(ConfigError::MissingField(key.to_string()))
1341
1406
  }
1342
- ```
1343
-
1344
- (Do) Use filter and filter_map for conditional processing.
1345
- ```rust
1407
+ """
1408
+ ] ;
1409
+ cs:do [
1410
+ cs:description "Use filter and filter_map for conditional processing." ;
1411
+ cs:language "rust" ;
1412
+ cs:code """
1346
1413
  fn find_active_user(id: UserId) -> Option<User> {
1347
1414
  users.get(&id).filter(|u| u.is_active)
1348
1415
  }
@@ -1352,20 +1419,23 @@ fn get_valid_entries(entries: &[Entry]) -> Vec<&Entry> {
1352
1419
  .filter(|e| e.is_valid())
1353
1420
  .collect()
1354
1421
  }
1355
- ```
1356
-
1357
- (Do) Use the ? operator with Option in functions returning Option.
1358
- ```rust
1422
+ """
1423
+ ] ;
1424
+ cs:do [
1425
+ cs:description "Use the ? operator with Option in functions returning Option." ;
1426
+ cs:language "rust" ;
1427
+ cs:code """
1359
1428
  fn get_nested_value(data: &Data) -> Option<&str> {
1360
1429
  let section = data.sections.get("main")?;
1361
1430
  let item = section.items.first()?;
1362
1431
  item.value.as_deref()
1363
1432
  }
1364
- ```
1365
- """ ;
1366
- cs:donts """
1367
- (Don't) Use sentinel values instead of Option.
1368
- ```rust
1433
+ """
1434
+ ] ;
1435
+ cs:dont [
1436
+ cs:description "Use sentinel values instead of Option." ;
1437
+ cs:language "rust" ;
1438
+ cs:code """
1369
1439
  // Bad: Magic values
1370
1440
  fn find_index(items: &[Item], target: &Item) -> i32 {
1371
1441
  // Returns -1 if not found - caller might forget to check!
@@ -1376,10 +1446,12 @@ fn find_index(items: &[Item], target: &Item) -> i32 {
1376
1446
  fn find_index(items: &[Item], target: &Item) -> Option<usize> {
1377
1447
  items.iter().position(|i| i == target)
1378
1448
  }
1379
- ```
1380
-
1381
- (Don't) Overuse unwrap() or expect() outside of tests.
1382
- ```rust
1449
+ """
1450
+ ] ;
1451
+ cs:dont [
1452
+ cs:description "Overuse unwrap() or expect() outside of tests." ;
1453
+ cs:language "rust" ;
1454
+ cs:code """
1383
1455
  // Bad: Panics on None
1384
1456
  let user = users.get(id).unwrap();
1385
1457
  let name = user.name.as_ref().unwrap();
@@ -1387,10 +1459,12 @@ let name = user.name.as_ref().unwrap();
1387
1459
  // Good: Handle absence explicitly
1388
1460
  let user = users.get(id).ok_or(UserError::NotFound(id))?;
1389
1461
  let name = user.name.as_deref().unwrap_or("Anonymous");
1390
- ```
1391
-
1392
- (Don't) Check is_some/is_none then unwrap.
1393
- ```rust
1462
+ """
1463
+ ] ;
1464
+ cs:dont [
1465
+ cs:description "Check is_some/is_none then unwrap." ;
1466
+ cs:language "rust" ;
1467
+ cs:code """
1394
1468
  // Bad: Redundant check
1395
1469
  if value.is_some() {
1396
1470
  let v = value.unwrap(); // We just checked!
@@ -1404,10 +1478,12 @@ if let Some(v) = value {
1404
1478
 
1405
1479
  // Or even better for side effects
1406
1480
  value.map(process);
1407
- ```
1408
-
1409
- (Don't) Create deeply nested Option chains without combinators.
1410
- ```rust
1481
+ """
1482
+ ] ;
1483
+ cs:dont [
1484
+ cs:description "Create deeply nested Option chains without combinators." ;
1485
+ cs:language "rust" ;
1486
+ cs:code """
1411
1487
  // Bad: Nested matching
1412
1488
  match outer {
1413
1489
  Some(o) => match o.inner {
@@ -1425,8 +1501,8 @@ outer
1425
1501
  .and_then(|o| o.inner)
1426
1502
  .and_then(|i| i.value)
1427
1503
  .map(transform)
1428
- ```
1429
- """ .
1504
+ """
1505
+ ] .
1430
1506
 
1431
1507
  # =============================================================================
1432
1508
  # Standard 10: Property-Based Testing
@@ -1435,16 +1511,11 @@ outer
1435
1511
  cs:PropertyBasedTesting a cs:CodeStandard ;
1436
1512
  cs:name "rust/testing/property-based" ;
1437
1513
  cs:hasCategory cs:RustCategory ;
1438
- cs:description """Complement unit tests with property-based testing to verify invariants across many generated inputs. This QuickCheck-inspired approach catches edge cases that example-based tests miss.
1439
-
1440
- **Ratings:**
1441
- - Impact: A (catches edge cases unit tests miss)
1442
- - Feasibility: B (requires proptest/quickcheck; learning curve)
1443
- - Idiomaticity: A (growing Rust practice)
1444
- - FP Purity: S (QuickCheck heritage; declarative testing)""" ;
1445
- cs:dos """
1446
- (Do) Use proptest for property-based testing.
1447
- ```rust
1514
+ cs:description """Complement unit tests with property-based testing to verify invariants across many generated inputs. This QuickCheck-inspired approach catches edge cases that example-based tests miss.""" ;
1515
+ cs:do [
1516
+ cs:description "Use proptest for property-based testing." ;
1517
+ cs:language "rust" ;
1518
+ cs:code """
1448
1519
  use proptest::prelude::*;
1449
1520
 
1450
1521
  proptest! {
@@ -1467,10 +1538,12 @@ proptest! {
1467
1538
  }
1468
1539
  }
1469
1540
  }
1470
- ```
1471
-
1472
- (Do) Test algebraic properties (identity, associativity, commutativity).
1473
- ```rust
1541
+ """
1542
+ ] ;
1543
+ cs:do [
1544
+ cs:description "Test algebraic properties (identity, associativity, commutativity)." ;
1545
+ cs:language "rust" ;
1546
+ cs:code """
1474
1547
  proptest! {
1475
1548
  #[test]
1476
1549
  fn merge_associative(a: Config, b: Config, c: Config) {
@@ -1487,10 +1560,12 @@ proptest! {
1487
1560
  assert_eq!(merged, config);
1488
1561
  }
1489
1562
  }
1490
- ```
1491
-
1492
- (Do) Test invariants that should hold for all valid inputs.
1493
- ```rust
1563
+ """
1564
+ ] ;
1565
+ cs:do [
1566
+ cs:description "Test invariants that should hold for all valid inputs." ;
1567
+ cs:language "rust" ;
1568
+ cs:code """
1494
1569
  proptest! {
1495
1570
  #[test]
1496
1571
  fn validated_email_contains_at(s in ".+@.+\\..+") {
@@ -1511,10 +1586,12 @@ proptest! {
1511
1586
  }
1512
1587
  }
1513
1588
  }
1514
- ```
1515
-
1516
- (Do) Use custom strategies for domain-specific types.
1517
- ```rust
1589
+ """
1590
+ ] ;
1591
+ cs:do [
1592
+ cs:description "Use custom strategies for domain-specific types." ;
1593
+ cs:language "rust" ;
1594
+ cs:code """
1518
1595
  fn valid_package_name() -> impl Strategy<Value = String> {
1519
1596
  "[a-z][a-z0-9-]{0,62}[a-z0-9]?"
1520
1597
  .prop_filter("Must not have consecutive hyphens", |s| !s.contains("--"))
@@ -1531,11 +1608,12 @@ proptest! {
1531
1608
  assert!(PackageName::new(&name).is_ok());
1532
1609
  }
1533
1610
  }
1534
- ```
1535
- """ ;
1536
- cs:donts """
1537
- (Don't) Only write example-based unit tests for complex logic.
1538
- ```rust
1611
+ """
1612
+ ] ;
1613
+ cs:dont [
1614
+ cs:description "Only write example-based unit tests for complex logic." ;
1615
+ cs:language "rust" ;
1616
+ cs:code """
1539
1617
  // Incomplete: Only tests a few examples
1540
1618
  #[test]
1541
1619
  fn test_merge() {
@@ -1545,10 +1623,12 @@ fn test_merge() {
1545
1623
  assert_eq!(merged.timeout, 20);
1546
1624
  // What about edge cases? Overflow? Default values?
1547
1625
  }
1548
- ```
1549
-
1550
- (Don't) Ignore test failures without understanding the counterexample.
1551
- ```rust
1626
+ """
1627
+ ] ;
1628
+ cs:dont [
1629
+ cs:description "Ignore test failures without understanding the counterexample." ;
1630
+ cs:language "rust" ;
1631
+ cs:code """
1552
1632
  // Bad: Ignoring failures
1553
1633
  proptest! {
1554
1634
  #[test]
@@ -1557,10 +1637,12 @@ proptest! {
1557
1637
  // ...
1558
1638
  }
1559
1639
  }
1560
- ```
1561
-
1562
- (Don't) Write properties that are too weak or tautological.
1563
- ```rust
1640
+ """
1641
+ ] ;
1642
+ cs:dont [
1643
+ cs:description "Write properties that are too weak or tautological." ;
1644
+ cs:language "rust" ;
1645
+ cs:code """
1564
1646
  // Bad: This always passes, tests nothing useful
1565
1647
  proptest! {
1566
1648
  #[test]
@@ -1577,10 +1659,12 @@ proptest! {
1577
1659
  assert_eq!(add(a, b), a + b); // Just restates the implementation
1578
1660
  }
1579
1661
  }
1580
- ```
1581
-
1582
- (Don't) Generate invalid inputs without proper filtering.
1583
- ```rust
1662
+ """
1663
+ ] ;
1664
+ cs:dont [
1665
+ cs:description "Generate invalid inputs without proper filtering." ;
1666
+ cs:language "rust" ;
1667
+ cs:code """
1584
1668
  // Bad: Generates invalid UTF-8 and panics
1585
1669
  proptest! {
1586
1670
  #[test]
@@ -1596,8 +1680,8 @@ proptest! {
1596
1680
  // ...
1597
1681
  }
1598
1682
  }
1599
- ```
1600
- """ .
1683
+ """
1684
+ ] .
1601
1685
 
1602
1686
  # =============================================================================
1603
1687
  # Standard 11: TDD Red-Green-Refactor
@@ -1606,16 +1690,11 @@ proptest! {
1606
1690
  cs:TDDRedGreenRefactor a cs:CodeStandard ;
1607
1691
  cs:name "rust/testing/tdd-red-green-refactor" ;
1608
1692
  cs:hasCategory cs:RustCategory ;
1609
- cs:description """Follow the strict TDD cycle: write a failing test first (red), implement minimally to pass (green), then refactor. This discipline ensures testability, prevents regression, and drives better design.
1610
-
1611
- **Ratings:**
1612
- - Impact: S (ensures testability, prevents regression)
1613
- - Feasibility: A (Rust's cargo test makes this easy)
1614
- - Idiomaticity: A (industry best practice)
1615
- - FP Purity: B (supports referential transparency goals)""" ;
1616
- cs:dos """
1617
- (Do) Write the test first, watch it fail.
1618
- ```rust
1693
+ cs:description """Follow the strict TDD cycle: write a failing test first (red), implement minimally to pass (green), then refactor. This discipline ensures testability, prevents regression, and drives better design.""" ;
1694
+ cs:do [
1695
+ cs:description "Write the test first, watch it fail." ;
1696
+ cs:language "rust" ;
1697
+ cs:code """
1619
1698
  // Step 1: RED - Write failing test
1620
1699
  #[cfg(test)]
1621
1700
  mod tests {
@@ -1631,10 +1710,12 @@ mod tests {
1631
1710
  }
1632
1711
 
1633
1712
  // At this point: cargo test fails - Version doesn't exist yet!
1634
- ```
1635
-
1636
- (Do) Implement the minimum code to pass.
1637
- ```rust
1713
+ """
1714
+ ] ;
1715
+ cs:do [
1716
+ cs:description "Implement the minimum code to pass." ;
1717
+ cs:language "rust" ;
1718
+ cs:code """
1638
1719
  // Step 2: GREEN - Minimal implementation
1639
1720
  pub struct Version {
1640
1721
  pub major: u32,
@@ -1657,10 +1738,12 @@ impl Version {
1657
1738
  }
1658
1739
 
1659
1740
  // cargo test passes!
1660
- ```
1661
-
1662
- (Do) Refactor while keeping tests green.
1663
- ```rust
1741
+ """
1742
+ ] ;
1743
+ cs:do [
1744
+ cs:description "Refactor while keeping tests green." ;
1745
+ cs:language "rust" ;
1746
+ cs:code """
1664
1747
  // Step 3: REFACTOR - Improve design
1665
1748
  impl Version {
1666
1749
  pub fn parse(s: &str) -> Result<Self, ParseError> {
@@ -1685,10 +1768,12 @@ impl Version {
1685
1768
  }
1686
1769
 
1687
1770
  // Tests still pass after refactoring!
1688
- ```
1689
-
1690
- (Do) Add tests for edge cases incrementally.
1691
- ```rust
1771
+ """
1772
+ ] ;
1773
+ cs:do [
1774
+ cs:description "Add tests for edge cases incrementally." ;
1775
+ cs:language "rust" ;
1776
+ cs:code """
1692
1777
  #[test]
1693
1778
  fn parse_rejects_empty_string() {
1694
1779
  assert!(Version::parse("").is_err());
@@ -1709,10 +1794,12 @@ fn parse_handles_leading_zeros() {
1709
1794
  let v = Version::parse("01.02.03").unwrap();
1710
1795
  assert_eq!(v.major, 1); // Decide: allow or reject?
1711
1796
  }
1712
- ```
1713
-
1714
- (Do) Use test modules colocated with implementation.
1715
- ```rust
1797
+ """
1798
+ ] ;
1799
+ cs:do [
1800
+ cs:description "Use test modules colocated with implementation." ;
1801
+ cs:language "rust" ;
1802
+ cs:code """
1716
1803
  // src/version.rs
1717
1804
  pub struct Version { /* ... */ }
1718
1805
 
@@ -1728,21 +1815,24 @@ mod tests {
1728
1815
  #[test]
1729
1816
  fn test_parse() { /* ... */ }
1730
1817
  }
1731
- ```
1732
- """ ;
1733
- cs:donts """
1734
- (Don't) Write implementation before tests.
1735
- ```rust
1818
+ """
1819
+ ] ;
1820
+ cs:dont [
1821
+ cs:description "Write implementation before tests." ;
1822
+ cs:language "rust" ;
1823
+ cs:code """
1736
1824
  // Bad: Implementation without tests
1737
1825
  pub fn complex_algorithm(input: &str) -> Result<Output, Error> {
1738
1826
  // 200 lines of complex logic
1739
1827
  // No tests to verify correctness
1740
1828
  // No tests to prevent regression
1741
1829
  }
1742
- ```
1743
-
1744
- (Don't) Write tests that pass trivially or test nothing.
1745
- ```rust
1830
+ """
1831
+ ] ;
1832
+ cs:dont [
1833
+ cs:description "Write tests that pass trivially or test nothing." ;
1834
+ cs:language "rust" ;
1835
+ cs:code """
1746
1836
  // Bad: Test that always passes
1747
1837
  #[test]
1748
1838
  fn useless_test() {
@@ -1754,10 +1844,12 @@ fn useless_test() {
1754
1844
  fn test_without_assertions() {
1755
1845
  let _ = Version::parse("1.0.0"); // No assertions!
1756
1846
  }
1757
- ```
1758
-
1759
- (Don't) Skip the refactor step.
1760
- ```rust
1847
+ """
1848
+ ] ;
1849
+ cs:dont [
1850
+ cs:description "Skip the refactor step." ;
1851
+ cs:language "rust" ;
1852
+ cs:code """
1761
1853
  // Bad: "It works, ship it!"
1762
1854
  impl Version {
1763
1855
  pub fn parse(s: &str) -> Result<Self, ParseError> {
@@ -1772,20 +1864,24 @@ impl Version {
1772
1864
  Ok(Self { major: a.unwrap(), minor: b.unwrap(), patch: c.unwrap() })
1773
1865
  }
1774
1866
  }
1775
- ```
1776
-
1777
- (Don't) Write tests after the fact that just confirm current behavior.
1778
- ```rust
1867
+ """
1868
+ ] ;
1869
+ cs:dont [
1870
+ cs:description "Write tests after the fact that just confirm current behavior." ;
1871
+ cs:language "rust" ;
1872
+ cs:code """
1779
1873
  // Bad: "Characterization tests" without understanding intent
1780
1874
  #[test]
1781
1875
  fn test_weird_behavior() {
1782
1876
  // I don't know why it returns 42, but it does, so test it
1783
1877
  assert_eq!(mysterious_function("input"), 42);
1784
1878
  }
1785
- ```
1786
-
1787
- (Don't) Test private implementation details.
1788
- ```rust
1879
+ """
1880
+ ] ;
1881
+ cs:dont [
1882
+ cs:description "Test private implementation details." ;
1883
+ cs:language "rust" ;
1884
+ cs:code """
1789
1885
  // Bad: Testing internals that may change
1790
1886
  #[test]
1791
1887
  fn test_internal_cache_structure() {
@@ -1802,5 +1898,5 @@ fn test_caching_behavior() {
1802
1898
  let result2 = obj.expensive_operation(); // Should be cached
1803
1899
  assert_eq!(result1, result2);
1804
1900
  }
1805
- ```
1806
- """ .
1901
+ """
1902
+ ] .