Agent skill
rust-module-organization-public-api-design
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/rust-module-organization-public-api-design
SKILL.md
priority: high
Rust Module Organization & Public API Design
Module Structure Principles
Modules are organizational units, not visibility boundaries. Use pub / pub(crate) / pub(super) to control visibility explicitly.
- Root crate module:
src/lib.rsorsrc/main.rsre-exports public items - Feature-specific modules:
src/parsing/,src/conversion/,src/utils/organize by domain - Internal vs. public: Mark private modules with
pub(crate)if they're internal infrastructure - Re-exports: Use
pub useinlib.rsto define the public API surface
Module Hierarchy Example
src/
├── lib.rs # Root: re-exports public API
├── parsing/ # Feature module
│ ├── mod.rs # Module interface, re-exports
│ ├── html_parser.rs # Internal implementation
│ ├── sanitizer.rs # Internal implementation
│ └── error.rs # Domain-specific errors
├── conversion/ # Feature module
│ ├── mod.rs # Module interface
│ ├── node_visitor.rs # Implementation
│ └── formatters/ # Submodule
│ ├── mod.rs
│ ├── markdown.rs
│ └── text.rs
├── error.rs # Crate-wide error types
└── config.rs # Configuration
Public API Design
Golden Rule: Your src/lib.rs should read like a user guide.
//! html-to-markdown: Convert HTML to Markdown with safety & performance
//!
//! # Quick Start
//! ```
//! use html_to_markdown::HtmlConverter;
//! let converter = HtmlConverter::new();
//! let markdown = converter.convert("<h1>Hello</h1>")?;
//! ```
// Public types
pub use crate::conversion::{HtmlConverter, ConversionConfig};
pub use crate::parsing::ParseError;
pub use crate::error::Error;
// Public type aliases for convenience
pub type Result<T> = std::result::Result<T, Error>;
// Don't re-export implementation details
// pub use crate::parsing::html_parser; // BAD
Pub/Private Boundaries
Public (pub): Part of stable API contract
- Main types:
HtmlConverter,ConversionConfig - Error types:
ParseError,ConversionError - Common utility functions
- Config builders
Pub(crate) (pub(crate)): Internal infrastructure, not for external users
pub(crate) struct InternalParser { ... }
pub(crate) fn internal_helper() { ... }
Private (no visibility keyword): Never exposed
fn internal_detail() { ... }
struct InternalState { ... }
Feature Gates
Use Cargo features to conditionally expose API surface:
[features]
default = ["parsing"]
parsing = []
async-runtime = ["tokio"]
ffi = ["libffi"]
[[example]]
name = "async_convert"
required-features = ["async-runtime"]
// Conditional re-export
#[cfg(feature = "async-runtime")]
pub use crate::async_convert::AsyncConverter;
#[cfg(feature = "ffi")]
pub use crate::ffi::ExportedAPI;
Module Re-exports Pattern
Module interface (src/parsing/mod.rs):
//! HTML parsing module with robust error handling
mod html_parser;
mod sanitizer;
pub mod error;
pub use self::html_parser::{Parser, ParseConfig};
pub use self::sanitizer::Sanitizer;
// Internal utilities not re-exported
use self::html_parser::HtmlToken;
Root re-export (src/lib.rs):
pub use crate::parsing::{Parser, ParseConfig};
pub use crate::parsing::error::{ParseError, SyntaxError};
pub use crate::conversion::HtmlConverter;
// Don't expose internal modules
// Bad: pub mod parsing;
Cargo Public API Validation
Use cargo-public-api to track breaking changes:
# Generate baseline
cargo public-api --baseline > baseline.txt
# Check for breaking changes in PRs
cargo public-api --diff baseline.txt
# Track additions
cargo public-api > current.txt
Add to CI:
- name: Check public API
run: |
cargo install cargo-public-api
cargo public-api --diff baseline.txt
Anti-Patterns to Avoid
-
Exposing implementation details:
rust// BAD: Leaks internal parser state pub use crate::parsing::internal::ParserState; // GOOD: Expose stable interface pub fn parse(input: &str) -> Result<Document> { ... } -
Deeply nested modules in public API:
rust// BAD: Public `mod parsing { mod html_parser { ... } }` // Users write: html_to_markdown::parsing::html_parser::Parser // GOOD: Flatten in re-export // Users write: html_to_markdown::Parser -
Mixed public/private in single module:
rust// BAD: Confusing what's stable pub fn stable_api() { ... } pub fn internal_detail() { ... } // GOOD: Separate concerns pub mod api { pub fn stable() { ... } } mod internal { pub(crate) fn detail() { ... } } -
Not documenting API stability:
rust// BAD: Users don't know if this might change pub struct Config { ... } // GOOD: Clear stability guarantees /// Configuration for HTML conversion (part of stable 1.x API) pub struct Config { ... }
Documentation Best Practices
- Module docs: Explain purpose and common patterns
- Type docs: Document public invariants
- Example code: Show real usage patterns
- Error docs: Document error conditions
/// Converts HTML to Markdown
///
/// # Examples
/// ```
/// use html_to_markdown::HtmlConverter;
/// let converter = HtmlConverter::new();
/// let md = converter.convert("<h1>Title</h1><p>Content</p>")?;
/// assert!(md.contains("# Title"));
/// ```
///
/// # Errors
/// Returns `ParseError` if HTML is malformed beyond recovery.
pub fn convert(html: &str) -> Result<String> { ... }
Stability Markers
Document API stability intentions:
// Stable in 1.x API
/// Configuration builder (stable API)
pub struct ConfigBuilder { ... }
// Experimental, may change
/// Experimental: May change in minor versions
#[doc(alias = "deprecated")]
pub struct ExperimentalFeature { ... }
// Deprecated
/// Deprecated: Use `convert_v2()` instead
#[deprecated(since = "0.5.0", note = "use convert_v2")]
pub fn convert_old(input: &str) -> Result<String> { ... }
Cross-references to Related Skills
- workspace-dependency-management: Coordinating public API across crates
- error-handling-strategy: Designing error types as part of public API
- testing-philosophy-coverage: Testing public API surface
Didn't find tool you were looking for?