# Pill Project Coding Standards This document defines coding standards for the Pill project, ensuring consistency, readability, maintainability, and idiomatic language practices. ## General Principles These principles guide every decision in the codebase. When a rule isn't clear, fall back to these. | # | Principle | Meaning | |---|-----------|---------| | 1 | Safety first | Prefer safe Rust. Only reach for `unsafe` when performance measurements or FFI boundaries leave no alternative. | | 2 | Idiomatic Rust | Follow standard library conventions. Avoid clever abstractions that fight the borrow checker or obscure intent. | | 3 | Clarity over brevity | Write code for the next person reading it. Use descriptive names, extract complex expressions, and comment the *why*. | | 4 | Explicit imports | Always import types, functions, and macros at the top of the file. Never rely on wildcard or implicit imports - `use anyhow::Result;`, not `use anyhow::*;`. | ## Code Style & Naming ### Naming Names are documentation. Choose them carefully. | Category | Case | Example | |----------|------|---------| | Types (structs, enums, traits) | `PascalCase` | `PillRecipe`, `ProductionLine` | | Functions, variables, modules | `snake_case` | `validate_recipe`, `batch_id` | | Constants & statics | `SCREAMING_SNAKE_CASE` | `MAX_BATCH_SIZE`, `NEXT_BATCH_ID` | | Generics | short uppercase, or descriptive | `T`, `E`, `Response` | | Resources (textures, sounds, models) | `snake_case` | `player_walk.png`, `explosion.wav` | | Shader/material parameters | `snake_case` | `base_color`, `roughness` | Abbreviations are forbidden. Shortening a word saves the writer a moment but costs every reader who has to decode it later. `env`, `dt`, `tex`, `ctx`, `fmt`, `rot`, `pos`, and any other truncation must never appear in identifiers, module paths, or resource filenames. The only permitted abbreviations are Rust language keywords whose canonical form is already short: `mut`, `ref`, `ptr`, `len`, `idx`, `dyn`. These are allowed because they are part of the language's vocabulary, not because they happen to be short. Long, verbose names are encouraged. `transform_component` is better than `trans_cmp`. Let the compiler do the typing; humans need to understand. ### Formatting - Run `cargo fmt` and `cargo clippy` before committing. Do not suppress clippy lints without documenting the reason. - Break long expressions into intermediate variables rather than chaining calls across multiple lines. - Keep function bodies short and single-purpose. If a function exceeds ~40 lines, consider extracting helpers. ## Project Structure ### Crates & Modules - Organize modules using clear, shallow directory hierarchies. - Every folder containing Rust source must have a `mod.rs` that defines the module's public API and re-exports relevant items. - Name crates and modules with `snake_case`: `pill_core`, `pill_render`. ### File Size & Cohesion - Keep files small and single-purpose. If a file exceeds ~500 lines, split it. - Each module should own one clear responsibility. If you struggle to summarise what a module does in one sentence, it does too much. ### Shared Utilities - Place cross-cutting helpers in `pill_core`. Do not duplicate utility functions across crates - centralise, then re-export. ## Source File Layout > **IMPORTANT - IMPORTANT - IMPORTANT** > > This section and the next one ([Comments & Documentation](#comments-&-documentation)) are strongly interconnected as the comments are also used to define the structure of the code file. > > It is highly recommended to read both sections first and immediately after see full single-block example in [Example Source File](#example-source-file) section. Every `.rs` file follows a fixed top-to-bottom order. This section defines what goes where - separators, section ordering, and structural rules. For how to write the comments themselves, see [Comments & Documentation](#comments--documentation).
Header Comment
file description
↓
Module-Level Attributes
↓
Includes
std
↓
external crates
↓
current crate
↓
Constants
static & const values
↓
Types Declaration
structs, enums, traits
↓
Type Implementations
methods & trait impls
repeat as needed
↓
General Functions
free functions & helpers
↓
Tests
unit & integration tests
The example used throughout is a hypothetical `pill_factory` module - a self-contained illustration of the layout rules. --- ### 1. Header Comment The file must open with a `//!` module-level doc comment. For the required structure and writing rules, see [Module-Level Comments](#module-level-comments) --- ### 2. Module-Level Attributes Optional. Contains all the attributions required. ```rust #![warn(missing_docs)] #![warn(clippy::pedantic)] #![deny(unsafe_code)] ``` --- ### 3. Includes Contains all the includes required in the file. ```rust // Standard library use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; // External crates use anyhow::{ensure, Result}; use serde::{Deserialize, Serialize}; // Current crate use crate::inventory::IngredientInventory; use crate::quality::QualityInspector; ``` - Three groups, always in this order: 1. Standard library (`std::…`) 2. External crates (third-party dependencies) 3. Current crate (`crate::…`) - Each group is separated by a blank line and preceded by a `// Group name` comment. - Within a group, imports are not further sorted. They follow logical proximity. --- ### 4. Constants Optional. Contains all `const` and `static` items. - Opened with a double-bar separator: `// =====…` (80 col). - `pub` items appear before private ones. - For doc-comment rules, see [Constants & Statics](#constants--statics). --- ### 5. Types & Implementations Core of the file. Groups of multiple type declarations and implementations right below them. Groups repeat multiple times. Each type group follows a fixed sub-order: 1. Type declaration - `struct`, `enum`, or `trait`. 2. Inherent `impl` block(s) - methods on the type itself. 3. Trait `impl` block(s) - trait implementations for the type. #### 5a. Type Declaration - Separator comment names the type (`// =====…`). - Derive macros go on the same line as `#[derive(…)]`. - For doc-comment rules, see [Types](#types-structs-enums-traits). #### 5b. Inherent Implementation - Single-dash separator (`// -----…`) divides methods into logical groups (e.g. *Construction*, *Property getters*, *Production*). - Groups appear in order of typical call-site usage: construct first, then queries, then mutations. - For doc-comment and step-label rules, see [Functions & Methods](#functions--methods) and [Step Labels](#step-labels). #### 5c. Trait Implementation - Trait impls come after all inherent impl blocks for the same type. - For doc-comment rules, see [Functions & Methods](#functions--methods). --- ### 6. General Functions - Free functions and module-level helpers that do not belong to any single type live here. - For doc-comment, step-label, and safety-comment rules, see [Functions & Methods](#functions--methods), [Step Labels](#step-labels), and [Safety Comments](#safety-comments). --- ### 7. Tests - Always the last section in the file. - Wrapped in `#[cfg(test)] mod tests { … }` with `use super::*;`. - Test helpers (non-`#[test]` functions) appear before the test cases. - Test function names describe the behaviour being verified, not the implementation detail. - For doc-comment and step-label rules, see [Functions & Methods](#functions--methods) and [Step Labels](#step-labels). --- ### Quick Reference | # | Section | Separator | Contents | |---|---------|-----------|----------| | 1 | Header Comment | *(none)* | `//!` module doc | | 2 | Attributes | *(none)* | `#![]` crate-level lints | | 3 | Includes | `// Group` comments | `std` → extern → `crate` | | 4 | Constants | `// ====…====` | `const` & `static` | | 5 | Types + Impls | `// ====…====` per type | struct/enum/trait → `impl` → trait `impl` | | 6 | General Functions | `// ====…====` | free functions & helpers | | 7 | Tests | `// ====…====` | `#[cfg(test)] mod tests` | ## Comments & Documentation This section is the single reference for how to write every kind of comment in a Pill source file. For where each comment kind belongs in the file layout, see [Source File Layout](#source-file-layout). ### Module-Level Comments Every file opens with a `//!` block. See the [Header Comment](#_1-header-comment) section in Source File Layout for the required structure (`# Responsibilities` / `# Design`). | Section | Required? | Purpose | |---------|-----------|---------| | One/Two-line summary | Yes | Few first lines; states what the file provides. | | `# Responsibilities` | Yes | Bullet list of what this module owns. | | `# Design` | Recommended | Architectural notes, cross-type relationships. | - Use intra-doc links to reference key types: `` [`PillFactory`] ``. --- ### Item-Level Comments Every public and private item carries a `///` doc comment. Private items use `///` when their behaviour is non-trivial. #### Types (structs, enums, traits) 2-4 lines: a 1-line summary followed by 1-3 lines of elaboration. ```rust /// Physical shape produced by the pill press. /// /// The shape determines the tooling used during manufacturing and influences /// the pill's appearance and ease of swallowing. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum PillShape { /// A conventional circular pill. Round, /// An elongated pill with rounded ends. Capsule, /// A pill divided by a central score line. Scored, } ``` - Summary line explains what the type represents. - Elaboration explains why it exists or its role in the system. - Enum variants each get their own `///` line. #### Functions & Methods ```rust /// Validates a pill recipe before production begins. /// /// # Errors /// /// Returns an error if the name is empty, the active ingredient is missing, or /// the recipe produces a pill with zero total weight. fn validate_recipe(recipe: &PillRecipe) -> Result<()> { /* … */ } ``` | Section | When to use | |---------|-------------| | *(summary line)* | Always. Describes what the function does. | | `# Errors` | When returning `Result`. Lists every error condition. | | `# Safety` | When the function is `unsafe`. Lists every caller obligation. | | `# Examples` | For key public API surfaces. Minimal working call. | - Summary uses third-person indicative: *"Validates a pill recipe"*, not *"Validate"*. - `# Errors` lists every way the function can fail, not just the common ones. - `# Safety` is a contract: violation means undefined behaviour. #### Constants & Statics ```rust /// Maximum number of pills permitted in a single batch. pub const MAX_BATCH_SIZE: u32 = 10_000; ``` - One `///` line stating what the value represents. - Mention external constraints if relevant (*"Limited by the press die diameter."*). --- ### Inline Comments Used for implementation detail - things a reader needs that don't belong in public API docs.(`//`) #### Step Labels Multi-phase functions label each phase with `// Step N: …`. - Use only when the function has 2+ distinct phases. - Each label sits on its own line immediately above the code it describes. - Aim for 2–4 steps. If you need 8+, split the function. #### Explanatory Comments Place a `//` comment above any block whose purpose isn't obvious from the code. ```rust // Convert the supplied name once and store its owned representation. let name = name.into(); // The subtraction is safe because the quality inspector cannot reject more // pills than were produced. let accepted_count = requested_count .checked_sub(rejected_count) .expect("quality inspector returned more rejections than produced pills"); ``` - Explain what and why - not how (the code already shows how). - Place on the line immediately above the code, no blank line between. - One comment per logical block; don't comment every line. - Avoid restating the obvious: `// increment i` is noise. --- ### Safety Comments Every `unsafe` block must be preceded by a `// SAFETY:` comment justifying why the operations are sound. ```rust // SAFETY: Disjoint per-pill access guaranteed by the single-threaded // factory scheduler. Both slices are valid for the lifetime of the // batch. The caller guarantees `inspection_results.len() == // acceptance_buffer.len()`, so unchecked indexing through // `get_unchecked` and `get_unchecked_mut` is sound. Mutating // `acceptance_buffer[index]` without atomics is safe because no // other thread observes this batch's inspection data. unsafe { let count = inspection_results.len(); for index in 0..count { let passed = *inspection_results.get_unchecked(index); if passed { *acceptance_buffer.get_unchecked_mut(index) += 1; } } } ``` Every `SAFETY` comment must address the relevant safety dimensions: | Category | Question the comment must answer | |----------|----------------------------------| | Aliasing | Why is there no overlapping `&mut` access? | | Lifetime | Why are pointers/references valid for the duration of use? | | Bounds | Why is unchecked indexing/pointer arithmetic within bounds? | | Concurrency | Why are atomics unnecessary (or, if used, why is the ordering correct)? | | Type validity | Why does the memory represent a valid instance of the target type? | | Invariants | What caller guarantees or local invariants does this code rely on? | - `// SAFETY:` uses UPPERCASE and a colon. - It appears inside the function body, immediately before `unsafe {`. - Written in prose, not bullet points. - For `pub unsafe` functions, the `/// # Safety` section documents caller obligations; the `// SAFETY:` inside the body documents why this implementation satisfies them. ### Examples in Comments Runnable `# Examples` sections in `///` doc comments are welcome - they double as documentation and as compile-tested usage samples. While not required for every item, they are especially valuable on key public types and functions. ```rust /// Coordinates inventory, manufacturing, and quality inspection. /// /// Owns the ingredient inventory and quality inspector and orchestrates /// batch production through the [`ProductionLine`] trait. /// /// # Examples /// /// ``` /// # use pill_factory::{PillFactory, PillRecipe, PillShape, ProductionLine}; /// # use pill_factory::inventory::IngredientInventory; /// # use pill_factory::quality::QualityInspector; /// let inventory = IngredientInventory::default(); /// let inspector = QualityInspector::new(/* … */); /// let mut factory = PillFactory::new(inventory, inspector); /// /// let recipe = PillRecipe::new("Aspirin", PillShape::Round, 100, 40, 10); /// let report = factory.manufacture(&recipe, 1_000)?; /// /// assert!(report.accepted_count() > 0); /// # Ok::<(), anyhow::Error>(()) /// ``` pub struct PillFactory { /* … */ } ``` Rules: - The example must compile - `cargo test` runs doc-tests by default. - Use `# ` to hide setup lines (imports, helper construction) from the rendered output while keeping them visible in the source. - Keep examples minimal: construct one instance, call one method, assert one outcome. - If the example can't compile in a doc-test (e.g. references a fictional crate path), wrap it in ` ```ignore` instead of ` ```rust`. - The `# Examples` section goes after `# Errors` / `# Safety` (if present), immediately before the item definition. ### Test Documentation Every `#[test]` function carries a `///` doc comment with a fixed structure: 1. First line - states what the test verifies. Third-person indicative: *"Tests that removing a component migrates the entity to a new archetype."* 2. `This test verifies that:` - bullet list of every condition under test. Each bullet corresponds to an assertion or logical check in the test body. 3. `Expected results:` - bullet list of the concrete outcomes. These are machine-checkable: every line should map to an `assert!` / `assert_eq!`. ```rust /// Tests that removing a component migrates the entity to a new archetype. /// /// This test verifies that: /// - A component can be removed from an entity with multiple components /// - The entity is migrated to a new archetype without the removed component /// - Other components remain intact on the entity /// - The old archetype is automatically cleaned up when empty /// /// Expected results: /// - remove_component returns Ok(()) /// - The entity still exists in entity_locations /// - Only 1 archetype remains (the old one is deleted) /// - The new archetype does not contain the removed component type #[test] fn entity_migrates_archetype_on_component_removal() { /* … */ } ``` Test helpers (preset constructors, builders) only need a one-line `///` summary of what they create. ## Error Handling ### Recoverable Errors Use `Result` for any operation that can fail. Never use panics for expected failure modes - panics are for unrecoverable logic errors only. ```rust fn validate_recipe(recipe: &PillRecipe) -> Result<()> { ensure!(!recipe.name.trim().is_empty(), "recipe name cannot be empty"); ensure!(recipe.active_ingredient_mg > 0, "recipe must contain an active ingredient"); Ok(()) } ``` - In binary crates, prefer `anyhow::Result` and attach context with `.context("...")?` for richer diagnostics. - In library crates, define and return specific error types so callers can match on variants. - Use `bail!()` to early-exit with an error rather than constructing a `return Err(...)` by hand. ### Propagating Errors Use the `?` operator extensively. It converts error types automatically when the appropriate `From` impls exist and keeps the happy path clean. ```rust fn manufacture(&mut self, recipe: &PillRecipe, count: u32) -> Result { validate_recipe(recipe)?; validate_batch_size(count)?; self.inventory.reserve_ingredients(recipe, count)?; // … Ok(report) } ``` - Every fallible call on the happy path should end with `?`. - If you need to add context before propagating, use `.context("while validating the recipe")?` instead of a `match` block. ## Testing ### Structure Tests live in a `#[cfg(test)] mod tests { … }` block at the bottom of the file, with `use super::*;` to bring the parent module's items into scope. ```rust #[cfg(test)] mod tests { use super::*; /// Creates the standard recipe used by unit tests. fn test_recipe() -> PillRecipe { PillRecipe::new("Test Aspirin", PillShape::Round, 100, 40, 10) } /// Verifies that recipe weight includes every ingredient. #[test] fn calculates_total_pill_weight() { // Step 1: Create a recipe with a known material composition. let recipe = test_recipe(); // Step 2: Verify the combined material weight. assert_eq!(recipe.total_weight_mg(), 150); } } ``` - Test helpers (non-`#[test]` functions) appear before the test cases. - Each helper carries a `///` doc comment explaining the preset it creates. - If setup is shared by many tests, extract it into a helper rather than duplicating the setup in every test. ### What to Test - Pure functions and data transformations- these are the highest-value tests. Given known inputs, assert known outputs. - State transitions- create an object, call a mutating method, verify the object ends up in the expected state. - Edge cases- empty collections, null or zero values, boundary conditions, maximum capacities. - Error paths- verify that invalid operations return the expected error variant, not just that they fail. Avoid testing implementation details. Tests should verify observable behaviour and invariants- what the code does, not how it does it. ### Naming Test function names describe the behaviour being verified, not the implementation: ```rust // Good - describes the observable outcome fn recipe_weight_includes_every_ingredient() { /* … */ } fn validating_empty_recipe_name_fails() { /* … */ } // Bad - describes the implementation or is too vague fn test_validate_recipe() { /* … */ } fn test_case_4() { /* … */ } ``` ### Assertions - Use `assert_eq!` / `assert_ne!` with a descriptive message as the last argument. The message should explain what went wrong, not restate the assertion. - Use `assert!` with a message for boolean conditions. - For error variants, match against the exact enum variant rather than checking `is_err()`. ```rust assert_eq!( result, Err(ValidationError::EmptyRecipeName), "validating a recipe with an empty name must return EmptyRecipeName" ); ``` ### Doc-Tests `# Examples` sections in `///` doc comments are run by `cargo test` as doc-tests. They serve double duty: documentation for the reader and compile-checked usage samples for the test suite. They are fully optional - only add them where the value justifies the maintenance cost. - Prefer doc-tests for API-level behaviour that a user of the type or function would care about. - Keep them minimal: construct, call, assert - one interaction per example. - Use `# ` to hide setup from the rendered output while keeping it compile-tested. - If the example cannot compile in a doc-test, wrap it in ` ```ignore`. For the full rules, see [Examples in Comments](#examples-in-comments). ### Test Documentation For the full `///` doc-comment structure required on every `#[test]` function, see [Test Documentation](#test-documentation) in the Comments & Documentation section. ## Unsafe Code Prefer safe Rust. Only reach for `unsafe` when there is no safe alternative and the performance gain or FFI requirement is measurable and necessary. `unsafe` does not mean the code is broken - it means the compiler cannot verify its correctness on its own. That responsibility falls to you: you must prove the code is sound and write that proof in a safety comment. When `unsafe` is unavoidable: - Encapsulate it. Wrap every `unsafe` block behind a safe abstraction. Callers should never need to write `unsafe { }` to use your API. - Document it. Every `unsafe` block and every `pub unsafe fn` must carry the appropriate safety documentation. See [Safety Comments](#safety-comments) for the full rules - they apply here without exception. If you find yourself reaching for `unsafe` to work around the borrow checker and you cannot verify the safety of the code, stop. Reconsider the design first. ## Logging & Observability ### Contextual Logging Always pass an explicit `LogContext` to logging macros. The context carries the subsystem name and enables per-subsystem log filtering at runtime. ```rust info!(ctx => self.log, "batch {} started; {} pills requested", batch_id, count); warn!(ctx => self.log, "quality inspector flagged {} anomalies", anomalies); ``` - Use the `"default"` context only when no subsystem context is available (e.g. one-shot CLI tools). Every long-lived component must own its context. - Configure per-context verbosity with filters like `ecs=debug,renderer=info`. Follow the standard log-level semantics: `error` → `warn` → `info` → `debug` → `trace`. ### Message Style - Write log messages in plain, descriptive English. No abbreviations. - Prefer specific values over vague descriptions: `"texture atlas full (2048×2048, 64 regions)"` not `"tex full"`. - Include relevant identifiers (`entity_id`, `batch_id`, `handle`) so logs are actionable during debugging. ### Performance Measurement - Use the project's `Timer` utility to instrument functions, subsystems, and critical paths. - Structure timing output with `begin_context` / `end_context` to produce hierarchical, readable profiles rather than flat lists of durations. ## Code Review Expectations Code review is a conversation, not a gate. Both authors and reviewers share responsibility for keeping it constructive and efficient. ### For Authors - Keep PRs small and focused on a single change. - For architectural or design changes, open an issue or draft PR early to gather feedback before investing in implementation details. ### For Reviewers - Review for correctness first: does the logic hold? Are edge cases handled? - Review for clarity: can you understand the intent without asking the author to explain? - Review for idiomatic usage: does the code follow the conventions in this document? - Approve when the change is correct, clear, and idiomatic - not when it matches your personal style. ### Merge Requirements A PR is ready to merge when: 1. All CI checks pass. 2. At least one reviewer has approved. 3. All review threads are resolved. ## Version Control ### Commit Messages - Write commit messages that explain what changed and why. The diff already shows *how*. - Keep commits atomic - each commit should do one thing and leave the project in a buildable state. It will also help the reviewers later on. - Never commit generated files (build artifacts, IDE settings, lock files from tools outside the project's standard dependency manager). ### Pull Request Titles Every PR title must follow the Conventional Commits format: ``` (): ``` | Type | Use for | |------|---------| | `feat` | New functionality | | `fix` | Bug fixes | | `docs` | Documentation changes | | `refactor` | Internal restructuring without behavioural changes | | `perf` | Performance improvements | | `test` | Adding or updating tests | | `build` | Dependency, compiler, or build-tooling changes | | `ci` | Continuous-integration changes | | `chore` | Other maintenance tasks | Examples: - `feat(renderer): add PBR material pipeline` - `fix(ecs): prevent archetype fragmentation on despawn` - `docs: document the build process` - `ci: add Clang builds to the matrix` ## Example Source File The code block below is a complete, self-contained example illustrating every layout rule, comment convention, and naming standard defined above - this is what a real Pill source file looks like in practice. ```rust //! Pill factory production and quality-control orchestration. //! //! # Responsibilities //! //! - Defines pill recipes and production results. //! - Manages ingredient inventory and production batches. //! - Coordinates pill pressing and quality inspection. //! - Reports accepted and rejected pill counts. //! //! # Design //! //! A [`PillFactory`] owns the services required to manufacture pills. //! Production is performed in discrete batches, and every batch receives a //! unique identifier before ingredients are consumed. #![warn(missing_docs)] #![warn(clippy::pedantic)] #![deny(unsafe_code)] // Standard library use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; // External crates use anyhow::{ensure, Result}; use serde::{Deserialize, Serialize}; // Current crate use crate::inventory::IngredientInventory; use crate::quality::QualityInspector; // ============================================================================= // Constants // ============================================================================= /// Maximum number of pills permitted in a single batch. pub const MAX_BATCH_SIZE: u32 = 10_000; /// Time required to press one pill. const PRESS_DURATION_PER_PILL: Duration = Duration::from_micros(250); /// Identifier assigned to the next production batch. static NEXT_BATCH_ID: AtomicU64 = AtomicU64::new(1); // ============================================================================= // PillShape // ============================================================================= /// Physical shape produced by the pill press. /// /// The shape determines the tooling used during manufacturing and influences /// the pill's appearance and ease of swallowing. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum PillShape { /// A conventional circular pill. Round, /// An elongated pill with rounded ends. Capsule, /// A pill divided by a central score line. Scored, } // ============================================================================= // PillRecipe // ============================================================================= /// Recipe used to manufacture a type of pill. /// /// Defines the material composition (active ingredient, filler, and coating) /// together with the physical shape produced by the pill press. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PillRecipe { name: String, shape: PillShape, active_ingredient_mg: u32, filler_mg: u32, coating_mg: u32, } impl PillRecipe { // ------------------------------------------------------------------------- // Construction // ------------------------------------------------------------------------- /// Creates a pill recipe. pub fn new( name: impl Into, shape: PillShape, active_ingredient_mg: u32, filler_mg: u32, coating_mg: u32, ) -> Self { // Convert the supplied name once and store its owned representation. let name = name.into(); Self { name, shape, active_ingredient_mg, filler_mg, coating_mg, } } // ------------------------------------------------------------------------- // Property getters // ------------------------------------------------------------------------- /// Returns the recipe's display name. pub fn name(&self) -> &str { &self.name } /// Returns the physical shape produced from the recipe. pub fn shape(&self) -> PillShape { self.shape } /// Returns the total material required for one pill, in milligrams. pub fn total_weight_mg(&self) -> u32 { // Combine every material included in a finished pill. self.active_ingredient_mg + self.filler_mg + self.coating_mg } } // ============================================================================= // ProductionReport // ============================================================================= /// Result of manufacturing one pill batch. /// /// Captures the batch identifier, accepted and rejected pill counts, and the /// estimated time spent on the production line. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProductionReport { batch_id: u64, requested_count: u32, accepted_count: u32, rejected_count: u32, production_duration: Duration, } impl ProductionReport { // ------------------------------------------------------------------------- // Properties // ------------------------------------------------------------------------- /// Returns the unique production batch identifier. pub fn batch_id(&self) -> u64 { self.batch_id } /// Returns the number of pills requested for the batch. pub fn requested_count(&self) -> u32 { self.requested_count } /// Returns the number of pills accepted by quality control. pub fn accepted_count(&self) -> u32 { self.accepted_count } /// Returns the number of pills rejected by quality control. pub fn rejected_count(&self) -> u32 { self.rejected_count } /// Returns the estimated production duration. pub fn production_duration(&self) -> Duration { self.production_duration } } // ============================================================================= // ProductionLine // ============================================================================= /// Interface implemented by pill-production facilities. /// /// Defines the contract for manufacturing pill batches. pub trait ProductionLine { /// Manufactures a batch according to the supplied recipe. /// /// # Errors /// /// Returns an error when the recipe is invalid, the requested batch size is /// unsupported, ingredients are unavailable, or quality inspection fails. fn manufacture( &mut self, recipe: &PillRecipe, requested_count: u32, ) -> Result; } // ============================================================================= // PillFactory // ============================================================================= /// Coordinates inventory, manufacturing, and quality inspection. /// /// Owns the ingredient inventory and quality inspector and orchestrates /// batch production through the [`ProductionLine`] trait. /// /// # Examples /// /// ``` /// # use pill_factory::{PillFactory, PillRecipe, PillShape, ProductionLine}; /// # use pill_factory::inventory::IngredientInventory; /// # use pill_factory::quality::QualityInspector; /// let inventory = IngredientInventory::default(); /// let inspector = QualityInspector::new(/* … */); /// let mut factory = PillFactory::new(inventory, inspector); /// /// let recipe = PillRecipe::new("Aspirin", PillShape::Round, 100, 40, 10); /// let report = factory.manufacture(&recipe, 1_000)?; /// /// assert!(report.accepted_count() > 0); /// # Ok::<(), anyhow::Error>(()) /// ``` pub struct PillFactory { inventory: IngredientInventory, quality_inspector: QualityInspector, } impl PillFactory { // ------------------------------------------------------------------------- // Construction // ------------------------------------------------------------------------- /// Creates a pill factory from its required production services. pub fn new( inventory: IngredientInventory, quality_inspector: QualityInspector, ) -> Self { Self { inventory, quality_inspector, } } } impl ProductionLine for PillFactory { /// Manufactures and inspects one batch of pills. /// /// # Errors /// /// Returns an error if validation, ingredient reservation, manufacturing, /// or quality inspection fails. fn manufacture( &mut self, recipe: &PillRecipe, requested_count: u32, ) -> Result { // Step 1: Validate inputs and reserve materials before changing state. validate_recipe(recipe)?; validate_batch_size(requested_count)?; self.inventory .reserve_ingredients(recipe, requested_count)?; // Step 2: Assign a batch identifier, then produce and inspect every pill. // Assign a unique batch identifier for this production run. The atomic let batch_id = NEXT_BATCH_ID.fetch_add(1, Ordering::Relaxed); // Manufacture the batch and inspect every pill for quality. let rejected_count = self .quality_inspector .inspect_batch(recipe, requested_count)?; // Step 3: Compute accepted count and commit the consumed ingredients. // The subtraction is safe because the quality inspector cannot reject more // pills than were produced. let accepted_count = requested_count .checked_sub(rejected_count) .expect("quality inspector returned more rejections than produced pills"); self.inventory .consume_reserved_ingredients(recipe, requested_count)?; // Step 4: Build the production report. let report = ProductionReport { batch_id, requested_count, accepted_count, rejected_count, production_duration: estimate_production_duration(requested_count), }; Ok(report) } } // ============================================================================= // General Functions // ============================================================================= /// Validates a pill recipe before production begins. /// /// # Errors /// /// Returns an error if the name is empty, the active ingredient is missing, or /// the recipe produces a pill with zero total weight. fn validate_recipe(recipe: &PillRecipe) -> Result<()> { // Step 1: Require a human-readable recipe name. ensure!( !recipe.name.trim().is_empty(), "pill recipe name cannot be empty" ); // Step 2: Require at least one milligram of active ingredient. ensure!( recipe.active_ingredient_mg > 0, "pill recipe must contain an active ingredient" ); // Step 3: Reject recipes that produce an empty pill. ensure!( recipe.total_weight_mg() > 0, "pill recipe must have a non-zero total weight" ); Ok(()) } /// Validates a requested production batch size. /// /// # Errors /// /// Returns an error if the requested count is zero or exceeds /// [`MAX_BATCH_SIZE`]. fn validate_batch_size(requested_count: u32) -> Result<()> { // Reject empty production batches. ensure!(requested_count > 0, "batch size must be greater than zero"); // Protect the production line from unsupported batch sizes. ensure!( requested_count <= MAX_BATCH_SIZE, "batch size cannot exceed {MAX_BATCH_SIZE}" ); Ok(()) } /// Estimates how long the pill press needs to manufacture a batch. fn estimate_production_duration(pill_count: u32) -> Duration { // Convert the pill count to the integer type expected by multiplication. let pill_count = u64::from(pill_count); // Calculate the duration without using floating-point arithmetic. PRESS_DURATION_PER_PILL.saturating_mul(pill_count as u32) } /// Counts accepted pills from a quality-inspection results buffer using /// unchecked indexing. Used on the hot path during batch manufacture to /// avoid bounds-checking overhead for every pill. /// /// # Safety /// /// The caller must guarantee that `inspection_results` and /// `acceptance_buffer` share the same length. Both pointers must be /// valid for the lifetime of the call. unsafe fn count_accepted_pills_unchecked( inspection_results: &[bool], acceptance_buffer: &mut [u32], ) -> u32 { // SAFETY: Disjoint per-pill access guaranteed by the single-threaded // factory scheduler. Both slices are valid for the lifetime of the // batch. The caller guarantees `inspection_results.len() == // acceptance_buffer.len()`, so unchecked indexing through // `get_unchecked` and `get_unchecked_mut` is sound. Mutating // `acceptance_buffer[index]` without atomics is safe because no // other thread observes this batch's inspection data. unsafe { let count = inspection_results.len(); let mut accepted_total: u32 = 0; for index in 0..count { // Read the inspection flag without bounds checking. let passed = *inspection_results.get_unchecked(index); if passed { // Increment the acceptance counter for this pill slot. *acceptance_buffer.get_unchecked_mut(index) += 1; accepted_total += 1; } } accepted_total } } // ============================================================================= // Tests // ============================================================================= #[cfg(test)] mod tests { use super::*; /// Creates the standard recipe used by unit tests. fn test_recipe() -> PillRecipe { PillRecipe::new( "Test Aspirin", PillShape::Round, 100, 40, 10, ) } /// Verifies that recipe weight includes every ingredient. #[test] fn calculates_total_pill_weight() { // Step 1: Create a recipe with a known material composition. let recipe = test_recipe(); // Step 2: Verify the combined material weight. assert_eq!(recipe.total_weight_mg(), 150); } /// Verifies that valid recipes pass validation. #[test] fn accepts_valid_recipe() { // Step 1: Create a complete pill recipe. let recipe = test_recipe(); // Step 2: Validate the recipe. let result = validate_recipe(&recipe); // Step 3: Confirm that validation succeeded. assert!(result.is_ok()); } } ```