Agent skill

rust-module-organization-public-api-design

Stars 163
Forks 31

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.

  1. Root crate module: src/lib.rs or src/main.rs re-exports public items
  2. Feature-specific modules: src/parsing/, src/conversion/, src/utils/ organize by domain
  3. Internal vs. public: Mark private modules with pub(crate) if they're internal infrastructure
  4. Re-exports: Use pub use in lib.rs to 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.

rust
//! 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

rust
pub(crate) struct InternalParser { ... }
pub(crate) fn internal_helper() { ... }

Private (no visibility keyword): Never exposed

rust
fn internal_detail() { ... }
struct InternalState { ... }

Feature Gates

Use Cargo features to conditionally expose API surface:

toml
[features]
default = ["parsing"]
parsing = []
async-runtime = ["tokio"]
ffi = ["libffi"]

[[example]]
name = "async_convert"
required-features = ["async-runtime"]
rust
// 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):

rust
//! 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):

rust
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:

bash
# 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:

yaml
- name: Check public API
  run: |
    cargo install cargo-public-api
    cargo public-api --diff baseline.txt

Anti-Patterns to Avoid

  1. 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> { ... }
    
  2. 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
    
  3. 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() { ... } }
    
  4. 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
rust
/// 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:

rust
// 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?

Be as detailed as possible for better results