@booklib/core 2.0.0
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/.cursor/rules/booklib-standards.mdc +40 -0
- package/.gemini/context.md +372 -0
- package/AGENTS.md +166 -0
- package/CHANGELOG.md +226 -0
- package/CLAUDE.md +81 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +304 -0
- package/LICENSE +21 -0
- package/PLAN.md +28 -0
- package/README.ja.md +198 -0
- package/README.ko.md +198 -0
- package/README.md +503 -0
- package/README.pt-BR.md +198 -0
- package/README.uk.md +241 -0
- package/README.zh-CN.md +198 -0
- package/SECURITY.md +9 -0
- package/agents/architecture-reviewer.md +136 -0
- package/agents/booklib-reviewer.md +90 -0
- package/agents/data-reviewer.md +107 -0
- package/agents/jvm-reviewer.md +146 -0
- package/agents/python-reviewer.md +128 -0
- package/agents/rust-reviewer.md +115 -0
- package/agents/ts-reviewer.md +110 -0
- package/agents/ui-reviewer.md +117 -0
- package/assets/logo.svg +36 -0
- package/bin/booklib-mcp.js +304 -0
- package/bin/booklib.js +1705 -0
- package/bin/skills.cjs +1292 -0
- package/booklib-router.mdc +36 -0
- package/booklib.config.json +19 -0
- package/commands/animation-at-work.md +10 -0
- package/commands/clean-code-reviewer.md +10 -0
- package/commands/data-intensive-patterns.md +10 -0
- package/commands/data-pipelines.md +10 -0
- package/commands/design-patterns.md +10 -0
- package/commands/domain-driven-design.md +10 -0
- package/commands/effective-java.md +10 -0
- package/commands/effective-kotlin.md +10 -0
- package/commands/effective-python.md +10 -0
- package/commands/effective-typescript.md +10 -0
- package/commands/kotlin-in-action.md +10 -0
- package/commands/lean-startup.md +10 -0
- package/commands/microservices-patterns.md +10 -0
- package/commands/programming-with-rust.md +10 -0
- package/commands/refactoring-ui.md +10 -0
- package/commands/rust-in-action.md +10 -0
- package/commands/skill-router.md +10 -0
- package/commands/spring-boot-in-action.md +10 -0
- package/commands/storytelling-with-data.md +10 -0
- package/commands/system-design-interview.md +10 -0
- package/commands/using-asyncio-python.md +10 -0
- package/commands/web-scraping-python.md +10 -0
- package/community/registry.json +1616 -0
- package/hooks/hooks.json +23 -0
- package/hooks/posttooluse-capture.mjs +67 -0
- package/hooks/suggest.js +153 -0
- package/lib/agent-behaviors.js +40 -0
- package/lib/agent-detector.js +96 -0
- package/lib/config-loader.js +39 -0
- package/lib/conflict-resolver.js +148 -0
- package/lib/context-builder.js +574 -0
- package/lib/discovery-engine.js +298 -0
- package/lib/doctor/hook-installer.js +83 -0
- package/lib/doctor/usage-tracker.js +87 -0
- package/lib/engine/ai-features.js +253 -0
- package/lib/engine/auditor.js +103 -0
- package/lib/engine/bm25-index.js +178 -0
- package/lib/engine/capture.js +120 -0
- package/lib/engine/corrections.js +198 -0
- package/lib/engine/doctor.js +195 -0
- package/lib/engine/graph-injector.js +137 -0
- package/lib/engine/graph.js +161 -0
- package/lib/engine/handoff.js +405 -0
- package/lib/engine/indexer.js +242 -0
- package/lib/engine/parser.js +53 -0
- package/lib/engine/query-expander.js +42 -0
- package/lib/engine/reranker.js +40 -0
- package/lib/engine/rrf.js +59 -0
- package/lib/engine/scanner.js +151 -0
- package/lib/engine/searcher.js +139 -0
- package/lib/engine/session-coordinator.js +306 -0
- package/lib/engine/session-manager.js +429 -0
- package/lib/engine/synthesizer.js +70 -0
- package/lib/installer.js +70 -0
- package/lib/instinct-block.js +33 -0
- package/lib/mcp-config-writer.js +88 -0
- package/lib/paths.js +57 -0
- package/lib/profiles/design.md +19 -0
- package/lib/profiles/general.md +16 -0
- package/lib/profiles/research-analysis.md +22 -0
- package/lib/profiles/software-development.md +23 -0
- package/lib/profiles/writing-content.md +19 -0
- package/lib/project-initializer.js +916 -0
- package/lib/registry/skills.js +102 -0
- package/lib/registry-searcher.js +99 -0
- package/lib/rules/rules-manager.js +169 -0
- package/lib/skill-fetcher.js +333 -0
- package/lib/well-known-builder.js +70 -0
- package/lib/wizard/index.js +404 -0
- package/lib/wizard/integration-detector.js +41 -0
- package/lib/wizard/project-detector.js +100 -0
- package/lib/wizard/prompt.js +156 -0
- package/lib/wizard/registry-embeddings.js +107 -0
- package/lib/wizard/skill-recommender.js +69 -0
- package/llms-full.txt +254 -0
- package/llms.txt +70 -0
- package/package.json +45 -0
- package/research-reports/2026-04-01-current-architecture.md +160 -0
- package/research-reports/IDEAS.md +93 -0
- package/rules/common/clean-code.md +42 -0
- package/rules/java/effective-java.md +42 -0
- package/rules/kotlin/effective-kotlin.md +37 -0
- package/rules/python/effective-python.md +38 -0
- package/rules/rust/rust.md +37 -0
- package/rules/typescript/effective-typescript.md +42 -0
- package/scripts/gen-llms-full.mjs +36 -0
- package/scripts/gen-og.mjs +142 -0
- package/scripts/validate-frontmatter.js +25 -0
- package/skills/animation-at-work/SKILL.md +270 -0
- package/skills/animation-at-work/assets/example_asset.txt +1 -0
- package/skills/animation-at-work/evals/evals.json +44 -0
- package/skills/animation-at-work/evals/results.json +13 -0
- package/skills/animation-at-work/examples/after.md +64 -0
- package/skills/animation-at-work/examples/before.md +35 -0
- package/skills/animation-at-work/references/api_reference.md +369 -0
- package/skills/animation-at-work/references/review-checklist.md +79 -0
- package/skills/animation-at-work/scripts/audit_animations.py +295 -0
- package/skills/animation-at-work/scripts/example.py +1 -0
- package/skills/clean-code-reviewer/SKILL.md +444 -0
- package/skills/clean-code-reviewer/audit.json +35 -0
- package/skills/clean-code-reviewer/evals/evals.json +185 -0
- package/skills/clean-code-reviewer/evals/results.json +13 -0
- package/skills/clean-code-reviewer/examples/after.md +48 -0
- package/skills/clean-code-reviewer/examples/before.md +33 -0
- package/skills/clean-code-reviewer/references/api_reference.md +158 -0
- package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
- package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
- package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
- package/skills/data-intensive-patterns/SKILL.md +267 -0
- package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
- package/skills/data-intensive-patterns/evals/evals.json +54 -0
- package/skills/data-intensive-patterns/evals/results.json +13 -0
- package/skills/data-intensive-patterns/examples/after.md +61 -0
- package/skills/data-intensive-patterns/examples/before.md +38 -0
- package/skills/data-intensive-patterns/references/api_reference.md +34 -0
- package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
- package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
- package/skills/data-intensive-patterns/scripts/adr.py +213 -0
- package/skills/data-intensive-patterns/scripts/example.py +1 -0
- package/skills/data-pipelines/SKILL.md +259 -0
- package/skills/data-pipelines/assets/example_asset.txt +1 -0
- package/skills/data-pipelines/evals/evals.json +45 -0
- package/skills/data-pipelines/evals/results.json +13 -0
- package/skills/data-pipelines/examples/after.md +97 -0
- package/skills/data-pipelines/examples/before.md +37 -0
- package/skills/data-pipelines/references/api_reference.md +301 -0
- package/skills/data-pipelines/references/review-checklist.md +181 -0
- package/skills/data-pipelines/scripts/example.py +1 -0
- package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
- package/skills/design-patterns/SKILL.md +271 -0
- package/skills/design-patterns/assets/example_asset.txt +1 -0
- package/skills/design-patterns/evals/evals.json +46 -0
- package/skills/design-patterns/evals/results.json +13 -0
- package/skills/design-patterns/examples/after.md +52 -0
- package/skills/design-patterns/examples/before.md +29 -0
- package/skills/design-patterns/references/api_reference.md +1 -0
- package/skills/design-patterns/references/patterns-catalog.md +726 -0
- package/skills/design-patterns/references/review-checklist.md +173 -0
- package/skills/design-patterns/scripts/example.py +1 -0
- package/skills/design-patterns/scripts/scaffold.py +807 -0
- package/skills/domain-driven-design/SKILL.md +142 -0
- package/skills/domain-driven-design/assets/example_asset.txt +1 -0
- package/skills/domain-driven-design/evals/evals.json +48 -0
- package/skills/domain-driven-design/evals/results.json +13 -0
- package/skills/domain-driven-design/examples/after.md +80 -0
- package/skills/domain-driven-design/examples/before.md +43 -0
- package/skills/domain-driven-design/references/api_reference.md +1 -0
- package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
- package/skills/domain-driven-design/references/review-checklist.md +158 -0
- package/skills/domain-driven-design/scripts/example.py +1 -0
- package/skills/domain-driven-design/scripts/scaffold.py +421 -0
- package/skills/effective-java/SKILL.md +227 -0
- package/skills/effective-java/assets/example_asset.txt +1 -0
- package/skills/effective-java/evals/evals.json +46 -0
- package/skills/effective-java/evals/results.json +13 -0
- package/skills/effective-java/examples/after.md +83 -0
- package/skills/effective-java/examples/before.md +37 -0
- package/skills/effective-java/references/api_reference.md +1 -0
- package/skills/effective-java/references/items-catalog.md +955 -0
- package/skills/effective-java/references/review-checklist.md +216 -0
- package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
- package/skills/effective-java/scripts/example.py +1 -0
- package/skills/effective-kotlin/SKILL.md +271 -0
- package/skills/effective-kotlin/assets/example_asset.txt +1 -0
- package/skills/effective-kotlin/audit.json +29 -0
- package/skills/effective-kotlin/evals/evals.json +45 -0
- package/skills/effective-kotlin/evals/results.json +13 -0
- package/skills/effective-kotlin/examples/after.md +36 -0
- package/skills/effective-kotlin/examples/before.md +38 -0
- package/skills/effective-kotlin/references/api_reference.md +1 -0
- package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
- package/skills/effective-kotlin/references/review-checklist.md +126 -0
- package/skills/effective-kotlin/scripts/example.py +1 -0
- package/skills/effective-python/SKILL.md +441 -0
- package/skills/effective-python/evals/evals.json +44 -0
- package/skills/effective-python/evals/results.json +13 -0
- package/skills/effective-python/examples/after.md +56 -0
- package/skills/effective-python/examples/before.md +40 -0
- package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
- package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
- package/skills/effective-python/ref-03-functions.md +186 -0
- package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
- package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
- package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
- package/skills/effective-python/ref-07-concurrency.md +213 -0
- package/skills/effective-python/ref-08-robustness-performance.md +248 -0
- package/skills/effective-python/ref-09-testing-debugging.md +253 -0
- package/skills/effective-python/ref-10-collaboration.md +175 -0
- package/skills/effective-python/references/api_reference.md +218 -0
- package/skills/effective-python/references/practices-catalog.md +483 -0
- package/skills/effective-python/references/review-checklist.md +190 -0
- package/skills/effective-python/scripts/lint.py +173 -0
- package/skills/effective-typescript/SKILL.md +262 -0
- package/skills/effective-typescript/audit.json +29 -0
- package/skills/effective-typescript/evals/evals.json +37 -0
- package/skills/effective-typescript/evals/results.json +13 -0
- package/skills/effective-typescript/examples/after.md +70 -0
- package/skills/effective-typescript/examples/before.md +47 -0
- package/skills/effective-typescript/references/api_reference.md +118 -0
- package/skills/effective-typescript/references/practices-catalog.md +371 -0
- package/skills/effective-typescript/scripts/review.py +169 -0
- package/skills/kotlin-in-action/SKILL.md +261 -0
- package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
- package/skills/kotlin-in-action/evals/evals.json +43 -0
- package/skills/kotlin-in-action/evals/results.json +13 -0
- package/skills/kotlin-in-action/examples/after.md +53 -0
- package/skills/kotlin-in-action/examples/before.md +39 -0
- package/skills/kotlin-in-action/references/api_reference.md +1 -0
- package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
- package/skills/kotlin-in-action/references/review-checklist.md +204 -0
- package/skills/kotlin-in-action/scripts/example.py +1 -0
- package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
- package/skills/lean-startup/SKILL.md +160 -0
- package/skills/lean-startup/assets/example_asset.txt +1 -0
- package/skills/lean-startup/evals/evals.json +43 -0
- package/skills/lean-startup/evals/results.json +13 -0
- package/skills/lean-startup/examples/after.md +80 -0
- package/skills/lean-startup/examples/before.md +34 -0
- package/skills/lean-startup/references/api_reference.md +319 -0
- package/skills/lean-startup/references/review-checklist.md +137 -0
- package/skills/lean-startup/scripts/example.py +1 -0
- package/skills/lean-startup/scripts/new_experiment.py +286 -0
- package/skills/microservices-patterns/SKILL.md +384 -0
- package/skills/microservices-patterns/evals/evals.json +45 -0
- package/skills/microservices-patterns/evals/results.json +13 -0
- package/skills/microservices-patterns/examples/after.md +69 -0
- package/skills/microservices-patterns/examples/before.md +40 -0
- package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
- package/skills/microservices-patterns/references/review-checklist.md +169 -0
- package/skills/microservices-patterns/scripts/new_service.py +583 -0
- package/skills/programming-with-rust/SKILL.md +209 -0
- package/skills/programming-with-rust/evals/evals.json +37 -0
- package/skills/programming-with-rust/evals/results.json +13 -0
- package/skills/programming-with-rust/examples/after.md +107 -0
- package/skills/programming-with-rust/examples/before.md +59 -0
- package/skills/programming-with-rust/references/api_reference.md +152 -0
- package/skills/programming-with-rust/references/practices-catalog.md +335 -0
- package/skills/programming-with-rust/scripts/review.py +142 -0
- package/skills/refactoring-ui/SKILL.md +362 -0
- package/skills/refactoring-ui/assets/example_asset.txt +1 -0
- package/skills/refactoring-ui/evals/evals.json +45 -0
- package/skills/refactoring-ui/evals/results.json +13 -0
- package/skills/refactoring-ui/examples/after.md +85 -0
- package/skills/refactoring-ui/examples/before.md +58 -0
- package/skills/refactoring-ui/references/api_reference.md +355 -0
- package/skills/refactoring-ui/references/review-checklist.md +114 -0
- package/skills/refactoring-ui/scripts/audit_css.py +250 -0
- package/skills/refactoring-ui/scripts/example.py +1 -0
- package/skills/rust-in-action/SKILL.md +350 -0
- package/skills/rust-in-action/evals/evals.json +38 -0
- package/skills/rust-in-action/evals/results.json +13 -0
- package/skills/rust-in-action/examples/after.md +156 -0
- package/skills/rust-in-action/examples/before.md +56 -0
- package/skills/rust-in-action/references/practices-catalog.md +346 -0
- package/skills/rust-in-action/scripts/review.py +147 -0
- package/skills/skill-router/SKILL.md +186 -0
- package/skills/skill-router/evals/evals.json +38 -0
- package/skills/skill-router/evals/results.json +13 -0
- package/skills/skill-router/examples/after.md +63 -0
- package/skills/skill-router/examples/before.md +39 -0
- package/skills/skill-router/references/api_reference.md +24 -0
- package/skills/skill-router/references/routing-heuristics.md +89 -0
- package/skills/skill-router/references/skill-catalog.md +174 -0
- package/skills/skill-router/scripts/route.py +266 -0
- package/skills/spring-boot-in-action/SKILL.md +340 -0
- package/skills/spring-boot-in-action/evals/evals.json +39 -0
- package/skills/spring-boot-in-action/evals/results.json +13 -0
- package/skills/spring-boot-in-action/examples/after.md +185 -0
- package/skills/spring-boot-in-action/examples/before.md +84 -0
- package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
- package/skills/spring-boot-in-action/scripts/review.py +184 -0
- package/skills/storytelling-with-data/SKILL.md +241 -0
- package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
- package/skills/storytelling-with-data/evals/evals.json +47 -0
- package/skills/storytelling-with-data/evals/results.json +13 -0
- package/skills/storytelling-with-data/examples/after.md +50 -0
- package/skills/storytelling-with-data/examples/before.md +33 -0
- package/skills/storytelling-with-data/references/api_reference.md +379 -0
- package/skills/storytelling-with-data/references/review-checklist.md +111 -0
- package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
- package/skills/storytelling-with-data/scripts/example.py +1 -0
- package/skills/system-design-interview/SKILL.md +233 -0
- package/skills/system-design-interview/assets/example_asset.txt +1 -0
- package/skills/system-design-interview/evals/evals.json +46 -0
- package/skills/system-design-interview/evals/results.json +13 -0
- package/skills/system-design-interview/examples/after.md +94 -0
- package/skills/system-design-interview/examples/before.md +27 -0
- package/skills/system-design-interview/references/api_reference.md +582 -0
- package/skills/system-design-interview/references/review-checklist.md +201 -0
- package/skills/system-design-interview/scripts/example.py +1 -0
- package/skills/system-design-interview/scripts/new_design.py +421 -0
- package/skills/using-asyncio-python/SKILL.md +290 -0
- package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
- package/skills/using-asyncio-python/evals/evals.json +43 -0
- package/skills/using-asyncio-python/evals/results.json +13 -0
- package/skills/using-asyncio-python/examples/after.md +68 -0
- package/skills/using-asyncio-python/examples/before.md +39 -0
- package/skills/using-asyncio-python/references/api_reference.md +267 -0
- package/skills/using-asyncio-python/references/review-checklist.md +149 -0
- package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
- package/skills/using-asyncio-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/SKILL.md +280 -0
- package/skills/web-scraping-python/assets/example_asset.txt +1 -0
- package/skills/web-scraping-python/evals/evals.json +46 -0
- package/skills/web-scraping-python/evals/results.json +13 -0
- package/skills/web-scraping-python/examples/after.md +109 -0
- package/skills/web-scraping-python/examples/before.md +40 -0
- package/skills/web-scraping-python/references/api_reference.md +393 -0
- package/skills/web-scraping-python/references/review-checklist.md +163 -0
- package/skills/web-scraping-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
- package/skills/writing-plans/audit.json +34 -0
- package/tests/agent-detector.test.js +83 -0
- package/tests/corrections.test.js +245 -0
- package/tests/doctor/hook-installer.test.js +72 -0
- package/tests/doctor/usage-tracker.test.js +140 -0
- package/tests/engine/benchmark-eval.test.js +31 -0
- package/tests/engine/bm25-index.test.js +85 -0
- package/tests/engine/capture-command.test.js +35 -0
- package/tests/engine/capture.test.js +17 -0
- package/tests/engine/graph-augmented-search.test.js +107 -0
- package/tests/engine/graph-injector.test.js +44 -0
- package/tests/engine/graph.test.js +216 -0
- package/tests/engine/hybrid-searcher.test.js +74 -0
- package/tests/engine/indexer-bm25.test.js +37 -0
- package/tests/engine/mcp-tools.test.js +73 -0
- package/tests/engine/project-initializer-mcp.test.js +99 -0
- package/tests/engine/query-expander.test.js +36 -0
- package/tests/engine/reranker.test.js +51 -0
- package/tests/engine/rrf.test.js +49 -0
- package/tests/engine/srag-prefix.test.js +47 -0
- package/tests/instinct-block.test.js +23 -0
- package/tests/mcp-config-writer.test.js +60 -0
- package/tests/project-initializer-new-agents.test.js +48 -0
- package/tests/rules/rules-manager.test.js +230 -0
- package/tests/well-known-builder.test.js +40 -0
- package/tests/wizard/integration-detector.test.js +31 -0
- package/tests/wizard/project-detector.test.js +51 -0
- package/tests/wizard/prompt-session.test.js +61 -0
- package/tests/wizard/prompt.test.js +16 -0
- package/tests/wizard/registry-embeddings.test.js +35 -0
- package/tests/wizard/skill-recommender.test.js +34 -0
- package/tests/wizard/slot-count.test.js +25 -0
- package/vercel.json +21 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# After: Rust in Action
|
|
2
|
+
|
|
3
|
+
The same utility rewritten with idiomatic systems Rust — explicit endianness, buffered I/O, a domain error type, checksum validation, thread pool, and safe shared state.
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
use std::fmt;
|
|
7
|
+
use std::fs::File;
|
|
8
|
+
use std::io::{BufReader, Read};
|
|
9
|
+
use std::path::Path;
|
|
10
|
+
use std::sync::{Arc, Mutex};
|
|
11
|
+
use std::sync::mpsc;
|
|
12
|
+
use std::thread;
|
|
13
|
+
|
|
14
|
+
// Domain error type — wraps all downstream errors (Ch 3, 8)
|
|
15
|
+
#[derive(Debug)]
|
|
16
|
+
pub enum LogError {
|
|
17
|
+
Io(std::io::Error),
|
|
18
|
+
InvalidRecord { offset: usize, reason: &'static str },
|
|
19
|
+
ChecksumMismatch { expected: u32, got: u32 },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl fmt::Display for LogError {
|
|
23
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
24
|
+
match self {
|
|
25
|
+
LogError::Io(e) => write!(f, "I/O: {e}"),
|
|
26
|
+
LogError::InvalidRecord { offset, reason } =>
|
|
27
|
+
write!(f, "invalid record at {offset}: {reason}"),
|
|
28
|
+
LogError::ChecksumMismatch { expected, got } =>
|
|
29
|
+
write!(f, "checksum mismatch: expected {expected:#010x}, got {got:#010x}"),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl std::error::Error for LogError {}
|
|
35
|
+
impl From<std::io::Error> for LogError {
|
|
36
|
+
fn from(e: std::io::Error) -> Self { LogError::Io(e) }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// BufReader for batched I/O syscalls; &Path for type-safe path (Ch 7)
|
|
40
|
+
// Returns Result — caller decides how to handle failure (Ch 3)
|
|
41
|
+
fn read_log(path: &Path) -> Result<Vec<u8>, LogError> {
|
|
42
|
+
let file = File::open(path)?; // ? converts io::Error → LogError (Ch 8)
|
|
43
|
+
let mut reader = BufReader::new(file); // batched reads (Ch 7)
|
|
44
|
+
let mut buf = Vec::new();
|
|
45
|
+
reader.read_to_end(&mut buf)?;
|
|
46
|
+
Ok(buf)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Explicit little-endian — deterministic across all hosts (Ch 5, 7)
|
|
50
|
+
fn parse_record_id(bytes: &[u8], offset: usize) -> Result<u32, LogError> {
|
|
51
|
+
bytes.get(offset..offset + 4)
|
|
52
|
+
.ok_or(LogError::InvalidRecord { offset, reason: "not enough bytes for id" })
|
|
53
|
+
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Simulate a simple CRC32-like checksum (Ch 7)
|
|
57
|
+
fn checksum(data: &[u8]) -> u32 {
|
|
58
|
+
data.iter().fold(0u32, |acc, &b| acc.wrapping_add(b as u32))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Explicit little-endian write + checksum (Ch 5, 7)
|
|
62
|
+
fn write_record(id: u32, data: &[u8]) -> Vec<u8> {
|
|
63
|
+
let mut out = Vec::with_capacity(4 + data.len() + 4);
|
|
64
|
+
out.extend_from_slice(&id.to_le_bytes()); // SAFETY: LE is the protocol spec
|
|
65
|
+
out.extend_from_slice(data);
|
|
66
|
+
let crc = checksum(&out);
|
|
67
|
+
out.extend_from_slice(&crc.to_le_bytes()); // append checksum
|
|
68
|
+
out
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn read_record(buf: &[u8]) -> Result<(u32, &[u8]), LogError> {
|
|
72
|
+
let id = parse_record_id(buf, 0)?;
|
|
73
|
+
let payload = buf.get(4..buf.len() - 4)
|
|
74
|
+
.ok_or(LogError::InvalidRecord { offset: 4, reason: "too short for payload + checksum" })?;
|
|
75
|
+
let stored_crc = u32::from_le_bytes(buf[buf.len() - 4..].try_into().unwrap());
|
|
76
|
+
let computed = checksum(&buf[..buf.len() - 4]);
|
|
77
|
+
if computed != stored_crc {
|
|
78
|
+
return Err(LogError::ChecksumMismatch { expected: stored_crc, got: computed });
|
|
79
|
+
}
|
|
80
|
+
Ok((id, payload))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Thread pool via channel — no unbounded thread spawning (Ch 10)
|
|
84
|
+
type Job = Box<dyn FnOnce() + Send + 'static>;
|
|
85
|
+
|
|
86
|
+
struct Pool {
|
|
87
|
+
tx: mpsc::Sender<Job>,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
impl Pool {
|
|
91
|
+
fn new(workers: usize) -> Self {
|
|
92
|
+
let (tx, rx) = mpsc::channel::<Job>();
|
|
93
|
+
let rx = Arc::new(Mutex::new(rx));
|
|
94
|
+
for _ in 0..workers {
|
|
95
|
+
let rx = Arc::clone(&rx);
|
|
96
|
+
thread::spawn(move || loop {
|
|
97
|
+
match rx.lock().expect("mutex poisoned").recv() {
|
|
98
|
+
Ok(job) => job(),
|
|
99
|
+
Err(_) => break, // sender dropped — shut down
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
Pool { tx }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn submit(&self, job: impl FnOnce() + Send + 'static) {
|
|
107
|
+
self.tx.send(Box::new(job)).expect("pool closed");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Safe shared error counter via Arc<Mutex<T>> (Ch 6, 10)
|
|
112
|
+
fn make_error_counter() -> Arc<Mutex<u32>> {
|
|
113
|
+
Arc::new(Mutex::new(0))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn record_error(counter: &Arc<Mutex<u32>>) {
|
|
117
|
+
*counter.lock().expect("mutex poisoned") += 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn main() -> Result<(), LogError> {
|
|
121
|
+
let path = Path::new("data.log");
|
|
122
|
+
let bytes = read_log(path)?;
|
|
123
|
+
|
|
124
|
+
let id = parse_record_id(&bytes, 0)?;
|
|
125
|
+
println!("id: {id}");
|
|
126
|
+
|
|
127
|
+
let pool = Pool::new(4);
|
|
128
|
+
let errors = make_error_counter();
|
|
129
|
+
|
|
130
|
+
// move — closure owns record + error counter clone (Ch 10)
|
|
131
|
+
let record = bytes.clone();
|
|
132
|
+
let err_counter = Arc::clone(&errors);
|
|
133
|
+
pool.submit(move || {
|
|
134
|
+
println!("processing {} bytes, id={}", record.len(), id);
|
|
135
|
+
if record.len() < 8 {
|
|
136
|
+
record_error(&err_counter);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Give threads time to finish (production code would use join handles)
|
|
141
|
+
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
142
|
+
println!("errors: {}", errors.lock().unwrap());
|
|
143
|
+
|
|
144
|
+
Ok(())
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Key improvements:**
|
|
149
|
+
- `LogError` wraps all downstream errors with `From` impls — `?` converts automatically (Ch 3, 8)
|
|
150
|
+
- `BufReader` batches file I/O syscalls — essential for large files (Ch 7)
|
|
151
|
+
- `u32::from_le_bytes()` / `to_le_bytes()` — explicit endianness, correct across all hosts (Ch 5)
|
|
152
|
+
- Checksum appended and verified on read — corruption is detectable (Ch 7)
|
|
153
|
+
- Thread pool via `mpsc::channel` + `Arc<Mutex<Receiver>>` — bounded concurrency (Ch 10)
|
|
154
|
+
- `Arc<Mutex<u32>>` replaces `unsafe static mut` — safe shared mutable state (Ch 6, 10)
|
|
155
|
+
- `move` closures transfer ownership into threads — required for `'static` bound (Ch 10)
|
|
156
|
+
- `&Path` instead of `String` for file path — type-safe, works with literals (Ch 7)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Before: Rust in Action
|
|
2
|
+
|
|
3
|
+
A systems utility that reads a binary log file, parses records, and processes them concurrently — with common systems-level anti-patterns.
|
|
4
|
+
|
|
5
|
+
```rust
|
|
6
|
+
use std::fs::File;
|
|
7
|
+
use std::io::Read;
|
|
8
|
+
use std::thread;
|
|
9
|
+
|
|
10
|
+
// Silently panics on any I/O failure; no error type
|
|
11
|
+
fn read_log(path: String) -> Vec<u8> {
|
|
12
|
+
let mut file = File::open(path).unwrap();
|
|
13
|
+
let mut buf = vec![];
|
|
14
|
+
file.read_to_end(&mut buf).unwrap();
|
|
15
|
+
buf
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Assumes native endianness — corrupts data on big-endian hosts
|
|
19
|
+
fn parse_record_id(bytes: &[u8]) -> u32 {
|
|
20
|
+
let arr = [bytes[0], bytes[1], bytes[2], bytes[3]];
|
|
21
|
+
u32::from_ne_bytes(arr) // native endian — wrong for protocol
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Spawns one thread per record — no pooling
|
|
25
|
+
fn process_all(records: Vec<Vec<u8>>) {
|
|
26
|
+
for record in records {
|
|
27
|
+
thread::spawn(|| {
|
|
28
|
+
println!("processing {} bytes", record.len());
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// No join — threads may not finish before main exits
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Shared mutable state with unsafe global
|
|
35
|
+
static mut ERROR_COUNT: u32 = 0;
|
|
36
|
+
|
|
37
|
+
fn record_error() {
|
|
38
|
+
unsafe {
|
|
39
|
+
ERROR_COUNT += 1; // data race — undefined behavior
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// No endianness comment, no checksum, no error type
|
|
44
|
+
fn write_record(id: u32, data: &[u8]) -> Vec<u8> {
|
|
45
|
+
let mut out = vec![];
|
|
46
|
+
out.extend_from_slice(&id.to_ne_bytes()); // native endian — wrong
|
|
47
|
+
out.extend_from_slice(data);
|
|
48
|
+
out // no checksum — corruption undetectable
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn main() {
|
|
52
|
+
let bytes = read_log(String::from("data.log"));
|
|
53
|
+
let id = parse_record_id(&bytes);
|
|
54
|
+
println!("id: {}", id);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Rust in Action — Practices Catalog
|
|
2
|
+
|
|
3
|
+
Systems-focused before/after examples from each chapter group.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Ownership: Use References, Not Moves (Ch 4)
|
|
8
|
+
|
|
9
|
+
**Before:**
|
|
10
|
+
```rust
|
|
11
|
+
fn print_log(data: Vec<u8>) { // consumes — caller loses data
|
|
12
|
+
println!("{} bytes", data.len());
|
|
13
|
+
}
|
|
14
|
+
let log = vec![1u8, 2, 3];
|
|
15
|
+
print_log(log);
|
|
16
|
+
// log is gone — can't use it again
|
|
17
|
+
```
|
|
18
|
+
**After:**
|
|
19
|
+
```rust
|
|
20
|
+
fn print_log(data: &[u8]) { // borrows a slice — Vec<u8> derefs to &[u8]
|
|
21
|
+
println!("{} bytes", data.len());
|
|
22
|
+
}
|
|
23
|
+
let log = vec![1u8, 2, 3];
|
|
24
|
+
print_log(&log);
|
|
25
|
+
println!("{log:?}"); // still valid
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Ownership: Resolving Lifetime Issues (Ch 4)
|
|
31
|
+
|
|
32
|
+
**Before:**
|
|
33
|
+
```rust
|
|
34
|
+
struct Config {
|
|
35
|
+
name: String, // owned — always heap-allocates
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
**After (borrow when caller controls data):**
|
|
39
|
+
```rust
|
|
40
|
+
struct Config<'a> {
|
|
41
|
+
name: &'a str, // borrows — zero allocation if caller has the string
|
|
42
|
+
}
|
|
43
|
+
// Use when Config doesn't outlive the string it references
|
|
44
|
+
```
|
|
45
|
+
**Or (own when Config must be independent):**
|
|
46
|
+
```rust
|
|
47
|
+
struct Config {
|
|
48
|
+
name: String, // owned — correct when Config outlives the source string
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Smart Pointers: Choosing the Right Type (Ch 6)
|
|
55
|
+
|
|
56
|
+
```rust
|
|
57
|
+
// Box<T>: heap allocation, single owner
|
|
58
|
+
let boxed: Box<[u8]> = vec![1, 2, 3].into_boxed_slice();
|
|
59
|
+
|
|
60
|
+
// Rc<T>: shared ownership, single thread only
|
|
61
|
+
use std::rc::Rc;
|
|
62
|
+
let shared = Rc::new(vec![1, 2, 3]);
|
|
63
|
+
let clone1 = Rc::clone(&shared); // cheap — increments refcount
|
|
64
|
+
// let _ = thread::spawn(move || { clone1; }); // COMPILE ERROR: Rc is not Send
|
|
65
|
+
|
|
66
|
+
// Arc<T>: shared ownership, multi-thread safe
|
|
67
|
+
use std::sync::Arc;
|
|
68
|
+
let arc = Arc::new(vec![1, 2, 3]);
|
|
69
|
+
let arc2 = Arc::clone(&arc); // idiomatic — explicit about cheapness
|
|
70
|
+
thread::spawn(move || println!("{arc2:?}")).join().unwrap();
|
|
71
|
+
|
|
72
|
+
// RefCell<T>: interior mutability, single thread, runtime checks
|
|
73
|
+
use std::cell::RefCell;
|
|
74
|
+
let cache: RefCell<Option<String>> = RefCell::new(None);
|
|
75
|
+
*cache.borrow_mut() = Some("computed".into());
|
|
76
|
+
|
|
77
|
+
// Cow<T>: clone-on-write — avoids allocation when only reading
|
|
78
|
+
use std::borrow::Cow;
|
|
79
|
+
fn process(input: &str) -> Cow<str> {
|
|
80
|
+
if input.contains("bad") {
|
|
81
|
+
Cow::Owned(input.replace("bad", "good")) // allocates only when needed
|
|
82
|
+
} else {
|
|
83
|
+
Cow::Borrowed(input) // zero allocation
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Data: Explicit Endianness (Ch 5, 7)
|
|
91
|
+
|
|
92
|
+
**Before:**
|
|
93
|
+
```rust
|
|
94
|
+
fn parse_id(bytes: &[u8]) -> u32 {
|
|
95
|
+
u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) // native — wrong on BE hosts
|
|
96
|
+
}
|
|
97
|
+
fn write_id(id: u32) -> [u8; 4] {
|
|
98
|
+
id.to_ne_bytes() // corrupts protocol on big-endian
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
**After:**
|
|
102
|
+
```rust
|
|
103
|
+
// Protocol says: little-endian. Be explicit regardless of host.
|
|
104
|
+
fn parse_id(bytes: &[u8], offset: usize) -> Result<u32, &'static str> {
|
|
105
|
+
bytes.get(offset..offset + 4)
|
|
106
|
+
.ok_or("buffer too short")
|
|
107
|
+
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn write_id(id: u32) -> [u8; 4] {
|
|
111
|
+
id.to_le_bytes() // always little-endian — deterministic on all hosts
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Data: Bit Manipulation (Ch 5)
|
|
118
|
+
|
|
119
|
+
```rust
|
|
120
|
+
// Named constants for masks — self-documenting (Ch 5)
|
|
121
|
+
const SIGN_BIT: u32 = 0x8000_0000;
|
|
122
|
+
const EXPONENT_MASK: u32 = 0x7F80_0000;
|
|
123
|
+
const MANTISSA_MASK: u32 = 0x007F_FFFF;
|
|
124
|
+
|
|
125
|
+
fn dissect_f32(n: f32) -> (u32, u32, u32) {
|
|
126
|
+
let bits = n.to_bits();
|
|
127
|
+
let sign = (bits & SIGN_BIT) >> 31;
|
|
128
|
+
let exponent = (bits & EXPONENT_MASK) >> 23;
|
|
129
|
+
let mantissa = bits & MANTISSA_MASK;
|
|
130
|
+
(sign, exponent, mantissa)
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Files: Buffered I/O + serde (Ch 7)
|
|
137
|
+
|
|
138
|
+
**Before:**
|
|
139
|
+
```rust
|
|
140
|
+
use std::fs::File;
|
|
141
|
+
use std::io::Read;
|
|
142
|
+
let mut f = File::open("data.bin").unwrap();
|
|
143
|
+
let mut buf = vec![];
|
|
144
|
+
f.read_to_end(&mut buf).unwrap(); // unbuffered + panics
|
|
145
|
+
```
|
|
146
|
+
**After:**
|
|
147
|
+
```rust
|
|
148
|
+
use std::fs::File;
|
|
149
|
+
use std::io::{BufReader, Read};
|
|
150
|
+
use std::path::Path;
|
|
151
|
+
|
|
152
|
+
fn read_file(path: &Path) -> Result<Vec<u8>, std::io::Error> {
|
|
153
|
+
let f = File::open(path)?;
|
|
154
|
+
let mut reader = BufReader::new(f); // batched syscalls
|
|
155
|
+
let mut buf = Vec::new();
|
|
156
|
+
reader.read_to_end(&mut buf)?;
|
|
157
|
+
Ok(buf)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Structured serialization with serde + bincode (Ch 7)
|
|
161
|
+
use serde::{Deserialize, Serialize};
|
|
162
|
+
|
|
163
|
+
#[derive(Serialize, Deserialize, Debug)]
|
|
164
|
+
struct Record {
|
|
165
|
+
id: u64,
|
|
166
|
+
payload: Vec<u8>,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn write_record(rec: &Record, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
|
170
|
+
let f = File::create(path)?;
|
|
171
|
+
let writer = std::io::BufWriter::new(f);
|
|
172
|
+
bincode::serialize_into(writer, rec)?;
|
|
173
|
+
Ok(())
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Error Handling: Library Error Wrapping (Ch 8)
|
|
180
|
+
|
|
181
|
+
```rust
|
|
182
|
+
// Wrapping multiple downstream error types in one domain error (Ch 8)
|
|
183
|
+
#[derive(Debug)]
|
|
184
|
+
pub enum NetworkError {
|
|
185
|
+
Io(std::io::Error),
|
|
186
|
+
AddrParse(std::net::AddrParseError),
|
|
187
|
+
Timeout,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
impl std::fmt::Display for NetworkError {
|
|
191
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
192
|
+
match self {
|
|
193
|
+
NetworkError::Io(e) => write!(f, "I/O: {e}"),
|
|
194
|
+
NetworkError::AddrParse(e) => write!(f, "address parse: {e}"),
|
|
195
|
+
NetworkError::Timeout => write!(f, "connection timed out"),
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
impl std::error::Error for NetworkError {}
|
|
201
|
+
|
|
202
|
+
impl From<std::io::Error> for NetworkError {
|
|
203
|
+
fn from(e: std::io::Error) -> Self { NetworkError::Io(e) }
|
|
204
|
+
}
|
|
205
|
+
impl From<std::net::AddrParseError> for NetworkError {
|
|
206
|
+
fn from(e: std::net::AddrParseError) -> Self { NetworkError::AddrParse(e) }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn connect(addr: &str) -> Result<std::net::TcpStream, NetworkError> {
|
|
210
|
+
let addr: std::net::SocketAddr = addr.parse()?; // AddrParseError → NetworkError
|
|
211
|
+
let stream = std::net::TcpStream::connect(addr)?; // io::Error → NetworkError
|
|
212
|
+
Ok(stream)
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Networking: State Machines with Enums (Ch 8)
|
|
219
|
+
|
|
220
|
+
```rust
|
|
221
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
222
|
+
enum TcpState {
|
|
223
|
+
Closed,
|
|
224
|
+
Listen,
|
|
225
|
+
SynReceived,
|
|
226
|
+
Established,
|
|
227
|
+
FinWait1,
|
|
228
|
+
Closed_,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
impl TcpState {
|
|
232
|
+
fn on_syn(self) -> Result<Self, &'static str> {
|
|
233
|
+
match self {
|
|
234
|
+
TcpState::Listen => Ok(TcpState::SynReceived),
|
|
235
|
+
other => Err("SYN received in invalid state"),
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fn on_ack(self) -> Result<Self, &'static str> {
|
|
240
|
+
match self {
|
|
241
|
+
TcpState::SynReceived => Ok(TcpState::Established),
|
|
242
|
+
other => Err("ACK received in invalid state"),
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Concurrency: Thread Pool via Channels (Ch 10)
|
|
251
|
+
|
|
252
|
+
```rust
|
|
253
|
+
use std::sync::{Arc, Mutex, mpsc};
|
|
254
|
+
use std::thread;
|
|
255
|
+
|
|
256
|
+
// move closure required — captures must be 'static (Ch 10)
|
|
257
|
+
fn spawn_worker(id: usize, rx: Arc<Mutex<mpsc::Receiver<String>>>) {
|
|
258
|
+
thread::spawn(move || loop { // move transfers rx into thread
|
|
259
|
+
let msg = rx.lock().expect("mutex poisoned").recv();
|
|
260
|
+
match msg {
|
|
261
|
+
Ok(s) => println!("worker {id}: {s}"),
|
|
262
|
+
Err(_) => break,
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fn main() {
|
|
268
|
+
let (tx, rx) = mpsc::channel::<String>();
|
|
269
|
+
let rx = Arc::new(Mutex::new(rx));
|
|
270
|
+
|
|
271
|
+
for i in 0..4 {
|
|
272
|
+
spawn_worker(i, Arc::clone(&rx));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
tx.send("hello".into()).unwrap();
|
|
276
|
+
tx.send("world".into()).unwrap();
|
|
277
|
+
drop(tx); // close channel — workers will exit their loops
|
|
278
|
+
thread::sleep(std::time::Duration::from_millis(10));
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Time: Instant vs SystemTime + NTP Offset (Ch 9)
|
|
285
|
+
|
|
286
|
+
```rust
|
|
287
|
+
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
|
288
|
+
|
|
289
|
+
// Instant for elapsed — monotonic, cannot go backwards (Ch 9)
|
|
290
|
+
let start = Instant::now();
|
|
291
|
+
do_work();
|
|
292
|
+
println!("elapsed: {:?}", start.elapsed());
|
|
293
|
+
|
|
294
|
+
// SystemTime for wall clock (can go backwards — don't use for elapsed)
|
|
295
|
+
let unix_ts = SystemTime::now()
|
|
296
|
+
.duration_since(UNIX_EPOCH)
|
|
297
|
+
.expect("system clock before Unix epoch")
|
|
298
|
+
.as_secs();
|
|
299
|
+
|
|
300
|
+
// NTP epoch conversion (Ch 9)
|
|
301
|
+
// NTP counts from 1900-01-01; Unix counts from 1970-01-01
|
|
302
|
+
const NTP_UNIX_OFFSET: u64 = 2_208_988_800;
|
|
303
|
+
|
|
304
|
+
fn ntp_to_unix(ntp_seconds: u64) -> u64 {
|
|
305
|
+
ntp_seconds.saturating_sub(NTP_UNIX_OFFSET)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn unix_to_ntp(unix_seconds: u64) -> u64 {
|
|
309
|
+
unix_seconds + NTP_UNIX_OFFSET
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Unsafe: Safe Abstraction Pattern (Ch 6)
|
|
316
|
+
|
|
317
|
+
```rust
|
|
318
|
+
// Wrap unsafe in a safe API — callers need not use unsafe (Ch 6)
|
|
319
|
+
pub struct AlignedBuffer {
|
|
320
|
+
ptr: *mut u8,
|
|
321
|
+
len: usize,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
impl AlignedBuffer {
|
|
325
|
+
pub fn new(len: usize) -> Self {
|
|
326
|
+
// SAFETY: len > 0, alignment is a power of 2, ptr checked for null
|
|
327
|
+
let layout = std::alloc::Layout::from_size_align(len, 64).unwrap();
|
|
328
|
+
let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
|
|
329
|
+
assert!(!ptr.is_null(), "allocation failed");
|
|
330
|
+
AlignedBuffer { ptr, len }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
pub fn as_slice(&self) -> &[u8] {
|
|
334
|
+
// SAFETY: ptr valid, len accurate, no mutable alias exists
|
|
335
|
+
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
impl Drop for AlignedBuffer {
|
|
340
|
+
fn drop(&mut self) {
|
|
341
|
+
// SAFETY: same layout as alloc, ptr not yet freed
|
|
342
|
+
let layout = std::alloc::Layout::from_size_align(self.len, 64).unwrap();
|
|
343
|
+
unsafe { std::alloc::dealloc(self.ptr, layout) }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
review.py — Pre-analysis script for Rust in Action reviews.
|
|
4
|
+
Usage: python review.py <file.rs>
|
|
5
|
+
|
|
6
|
+
Scans a Rust source file for systems-programming anti-patterns from the book:
|
|
7
|
+
endianness issues, unsafe shared state, missing buffered I/O, unwrap misuse,
|
|
8
|
+
unbounded thread spawning, and incorrect smart pointer choices.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
CHECKS = [
|
|
17
|
+
(
|
|
18
|
+
r"from_ne_bytes|to_ne_bytes",
|
|
19
|
+
"Ch 5/7: Native endianness",
|
|
20
|
+
"use from_le_bytes/to_le_bytes or from_be_bytes/to_be_bytes to match the protocol spec",
|
|
21
|
+
),
|
|
22
|
+
(
|
|
23
|
+
r"static\s+mut\s+\w+",
|
|
24
|
+
"Ch 6/10: static mut",
|
|
25
|
+
"data race risk — replace with Arc<Mutex<T>> or std::sync::atomic",
|
|
26
|
+
),
|
|
27
|
+
(
|
|
28
|
+
r"\.unwrap\(\)",
|
|
29
|
+
"Ch 3: .unwrap()",
|
|
30
|
+
"panics on failure — use ?, .expect(\"reason\"), or match in production paths",
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
r"unsafe\s*\{",
|
|
34
|
+
"Ch 6: unsafe block",
|
|
35
|
+
"ensure a safe abstraction wraps this; add a // SAFETY: comment explaining invariants",
|
|
36
|
+
),
|
|
37
|
+
(
|
|
38
|
+
r"File::open|File::create",
|
|
39
|
+
"Ch 7: Unbuffered file I/O",
|
|
40
|
+
"wrap in BufReader::new()/BufWriter::new() to batch syscalls",
|
|
41
|
+
),
|
|
42
|
+
(
|
|
43
|
+
r"thread::spawn",
|
|
44
|
+
"Ch 10: thread::spawn",
|
|
45
|
+
"if inside a loop, consider a thread pool (channel + Arc<Mutex<Receiver>>) instead of one thread per task",
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
r"\bRc\s*::\s*(new|clone)\b",
|
|
49
|
+
"Ch 6: Rc usage",
|
|
50
|
+
"Rc is not Send — if shared across threads, replace with Arc",
|
|
51
|
+
),
|
|
52
|
+
(
|
|
53
|
+
r"Box::new\(Vec\b|Box::new\(vec!",
|
|
54
|
+
"Ch 6: Box<Vec<T>>",
|
|
55
|
+
"Vec already heap-allocates — Box<Vec<T>> is double-indirection with no benefit; return Vec directly",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
r"\bexpect\s*\(\s*\)",
|
|
59
|
+
"Ch 3: .expect() with empty string",
|
|
60
|
+
"add a meaningful reason: .expect(\"what invariant was violated\")",
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def scan(source: str) -> list[dict]:
|
|
66
|
+
findings = []
|
|
67
|
+
lines = source.splitlines()
|
|
68
|
+
for lineno, line in enumerate(lines, start=1):
|
|
69
|
+
stripped = line.strip()
|
|
70
|
+
if stripped.startswith("//"):
|
|
71
|
+
continue # skip comments
|
|
72
|
+
for pattern, label, advice in CHECKS:
|
|
73
|
+
if re.search(pattern, line):
|
|
74
|
+
findings.append({
|
|
75
|
+
"line": lineno,
|
|
76
|
+
"text": line.rstrip(),
|
|
77
|
+
"label": label,
|
|
78
|
+
"advice": advice,
|
|
79
|
+
})
|
|
80
|
+
return findings
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def group_by_label(findings: list[dict]) -> dict:
|
|
84
|
+
groups: dict[str, list] = {}
|
|
85
|
+
for f in findings:
|
|
86
|
+
groups.setdefault(f["label"], []).append(f)
|
|
87
|
+
return groups
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def sep(char="-", width=70) -> str:
|
|
91
|
+
return char * width
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main() -> None:
|
|
95
|
+
if len(sys.argv) < 2:
|
|
96
|
+
print("Usage: python review.py <file.rs>")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
path = Path(sys.argv[1])
|
|
100
|
+
if not path.exists():
|
|
101
|
+
print(f"Error: file not found: {path}")
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
if path.suffix.lower() != ".rs":
|
|
105
|
+
print(f"Warning: expected a .rs file, got '{path.suffix}' — continuing anyway")
|
|
106
|
+
|
|
107
|
+
source = path.read_text(encoding="utf-8", errors="replace")
|
|
108
|
+
findings = scan(source)
|
|
109
|
+
groups = group_by_label(findings)
|
|
110
|
+
|
|
111
|
+
print(sep("="))
|
|
112
|
+
print("RUST IN ACTION — PRE-REVIEW REPORT")
|
|
113
|
+
print(sep("="))
|
|
114
|
+
print(f"File : {path}")
|
|
115
|
+
print(f"Lines : {len(source.splitlines())}")
|
|
116
|
+
print(f"Issues : {len(findings)} potential anti-patterns across {len(groups)} categories")
|
|
117
|
+
print()
|
|
118
|
+
|
|
119
|
+
if not findings:
|
|
120
|
+
print(" [OK] No common anti-patterns detected.")
|
|
121
|
+
print()
|
|
122
|
+
else:
|
|
123
|
+
for label, items in groups.items():
|
|
124
|
+
print(sep())
|
|
125
|
+
print(f" {label} ({len(items)} occurrence{'s' if len(items) != 1 else ''})")
|
|
126
|
+
print(sep())
|
|
127
|
+
print(f" Advice: {items[0]['advice']}")
|
|
128
|
+
print()
|
|
129
|
+
for item in items[:5]: # cap display at 5 per category
|
|
130
|
+
print(f" line {item['line']:>4}: {item['text'][:100]}")
|
|
131
|
+
if len(items) > 5:
|
|
132
|
+
print(f" ... and {len(items) - 5} more occurrence(s)")
|
|
133
|
+
print()
|
|
134
|
+
|
|
135
|
+
severity = (
|
|
136
|
+
"HIGH" if len(findings) >= 5
|
|
137
|
+
else "MEDIUM" if len(findings) >= 2
|
|
138
|
+
else "LOW" if findings
|
|
139
|
+
else "NONE"
|
|
140
|
+
)
|
|
141
|
+
print(sep("="))
|
|
142
|
+
print(f"SEVERITY: {severity} | Review chapters: Ch 3 (errors), Ch 5-7 (data/files), Ch 6 (pointers), Ch 10 (concurrency)")
|
|
143
|
+
print(sep("="))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
main()
|