Agents lang-rust-library-dev
Rust crate and library development patterns covering Cargo.toml for libraries, crate structure, module organization, rustdoc, publishing to crates.io, feature flags, semantic versioning, and API design. Use when creating a Rust library or crate, publishing to crates.io, documenting APIs with rustdoc, or managing library features and versions. This is the specialized skill for Rust library/crate development.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/lang-rust-library-dev" ~/.claude/skills/arustydev-agents-lang-rust-library-dev && rm -rf "$T"
content/skills/lang-rust-library-dev/SKILL.mdRust Library Development
Specialized patterns for creating, maintaining, and publishing Rust libraries and crates. This skill focuses on library-specific concerns like API design, documentation, versioning, and distribution.
Overview
┌──────────────────────────────────────────────────────────────┐ │ Rust Skill Hierarchy │ ├──────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────┐ │ │ │ lang-rust-dev │ │ │ │ (foundation) │ │ │ └──────────┬──────────┘ │ │ │ │ │ ┌───────────┬───────┼───────┬───────────┐ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ │ │ ┌────────┐ ┌────────┐ ... ┌─────────────────────┐ │ │ │ bin │ │testing │ │lang-rust-library-dev│ ◄─ HERE │ │ │ -dev │ │ -dev │ │ (library focus) │ │ │ └────────┘ └────────┘ └─────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘
This skill covers:
- Cargo.toml configuration for libraries
- Crate structure and module organization
- Public API design and stability
- Documentation with rustdoc
- Publishing to crates.io
- Feature flags and conditional compilation
- Semantic versioning (SemVer)
- Dependency management for libraries
- Examples, tests, and benches organization
This skill does NOT cover:
- Binary application development →
lang-rust-bin-dev - Testing strategies →
lang-rust-testing-dev - Async runtime patterns →
lang-rust-async-dev - General Rust syntax →
lang-rust-dev
Quick Reference
| Task | Command/Pattern |
|---|---|
| Create new library | |
| Build library | |
| Run library tests | |
| Generate docs | |
| Check API docs | |
| Publish to crates.io | |
| Feature flag | |
| Public re-export | |
| Doc example | |
| Hide from docs | |
Skill Routing
Use this table to find the right specialized skill:
| When you need to... | Use this skill |
|---|---|
| Create and publish Rust libraries | This skill () |
| Build CLI or binary applications | |
| Set up testing, mocking, property tests | |
| Work with async/await, tokio, futures | |
| Learn Rust syntax and fundamentals | |
Cargo.toml for Libraries
Basic Library Configuration
[package] name = "my-library" version = "0.1.0" edition = "2021" authors = ["Your Name <you@example.com>"] license = "MIT OR Apache-2.0" description = "A brief description of what this library does" documentation = "https://docs.rs/my-library" homepage = "https://github.com/user/my-library" repository = "https://github.com/user/my-library" readme = "README.md" keywords = ["keyword1", "keyword2", "keyword3"] categories = ["category1", "category2"] rust-version = "1.70.0" # Minimum supported Rust version (MSRV) [lib] name = "my_library" # Optional: overrides package name path = "src/lib.rs" # Default path crate-type = ["lib"] # Default for libraries [dependencies] # Regular dependencies serde = "1.0" # Optional dependencies (for features) tokio = { version = "1.0", optional = true } [dev-dependencies] # Test-only dependencies criterion = "0.5" proptest = "1.0" [features] # Feature flags default = ["std"] std = [] async = ["tokio", "tokio/rt-multi-thread"] serde = ["dep:serde", "serde/derive"] [package.metadata.docs.rs] # Documentation build configuration for docs.rs all-features = true rustdoc-args = ["--cfg", "docsrs"]
Dependency Specifications
[dependencies] # Version requirements exact = "=1.2.3" # Exact version caret = "^1.2.3" # Compatible: >=1.2.3 <2.0.0 (default) tilde = "~1.2.3" # Compatible: >=1.2.3 <1.3.0 wildcard = "1.*" # Any 1.x.x version range = ">=1.2, <1.5" # Version range # Git dependencies my-git-dep = { git = "https://github.com/user/repo" } my-git-dep-tag = { git = "https://github.com/user/repo", tag = "v1.0" } my-git-dep-rev = { git = "https://github.com/user/repo", rev = "abc123" } # Path dependencies (local development) my-local-crate = { path = "../my-local-crate" } # Optional dependencies (enabled via features) optional-dep = { version = "1.0", optional = true } # Dependency features serde = { version = "1.0", features = ["derive"], default-features = false }
Library Types
[lib] # Library output types crate-type = ["lib"] # Rust library (default) # crate-type = ["dylib"] # Dynamic library # crate-type = ["staticlib"] # Static library # crate-type = ["cdylib"] # C-compatible dynamic library # crate-type = ["rlib"] # Rust library (explicit) # For FFI libraries # crate-type = ["cdylib", "rlib"] # Both C-compatible and Rust library
Crate Structure
Standard Library Layout
my-library/ ├── Cargo.toml ├── Cargo.lock # Commit for binaries, gitignore for libraries ├── README.md ├── LICENSE-MIT ├── LICENSE-APACHE ├── CHANGELOG.md ├── src/ │ ├── lib.rs # Library root │ ├── prelude.rs # Common imports (optional) │ ├── error.rs # Error types │ ├── config.rs # Configuration │ ├── module1.rs # Top-level module │ ├── module2/ # Module with submodules │ │ ├── mod.rs # Module root │ │ ├── sub1.rs │ │ └── sub2.rs │ └── internal/ # Private internals │ ├── mod.rs │ └── helper.rs ├── examples/ # Usage examples │ ├── basic.rs │ └── advanced.rs ├── tests/ # Integration tests │ ├── integration_test.rs │ └── common/ # Test utilities │ └── mod.rs ├── benches/ # Benchmarks │ └── benchmark.rs └── docs/ # Additional documentation └── architecture.md
lib.rs - Library Root
//! # My Library //! //! This is the top-level documentation for the library. //! It appears on the crate's main documentation page. //! //! ## Quick Start //! //! ``` //! use my_library::Something; //! //! let thing = Something::new(); //! ``` // Deny warnings to catch issues early #![warn(missing_docs)] #![warn(missing_debug_implementations)] #![deny(unsafe_code)] // If appropriate // Feature attributes #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] // Public modules pub mod config; pub mod error; // Private internal modules mod internal; // Re-exports for convenience pub use error::{Error, Result}; pub use config::Config; // Prelude module (optional) pub mod prelude { //! Common imports for users of this library pub use crate::{Config, Error, Result}; pub use crate::something::Something; }
Module Organization
// src/module1.rs - Flat module file //! Module documentation pub struct PublicStruct { pub field: String, private_field: u32, } impl PublicStruct { /// Creates a new instance pub fn new(field: String) -> Self { Self { field, private_field: 0, } } } // Private helper fn internal_helper() -> u32 { 42 }
// src/module2/mod.rs - Module directory with submodules //! Module2 documentation mod sub1; mod sub2; // Re-export public items pub use sub1::PublicType1; pub use sub2::PublicType2; // Module-level items pub struct ModuleStruct;
Public API Design
Visibility and Re-exports
// src/lib.rs pub mod high_level { //! High-level API // Re-export from internal modules pub use crate::internal::core::CoreType; pub use crate::internal::utils::UtilType; } // Internal modules (not in public API) mod internal { pub(crate) mod core { // pub(crate) = visible within crate pub struct CoreType; } pub(crate) mod utils { pub struct UtilType; } } // Users access via: // use my_library::high_level::{CoreType, UtilType};
Prelude Pattern
// src/prelude.rs //! The prelude module re-exports commonly used items pub use crate::error::{Error, Result}; pub use crate::config::Config; pub use crate::builder::Builder; pub use crate::traits::{MyTrait, AnotherTrait}; // Users can import with: // use my_library::prelude::*;
Builder Pattern
/// Configuration builder for `MyType` #[derive(Debug, Default)] pub struct MyTypeBuilder { field1: Option<String>, field2: Option<u32>, } impl MyTypeBuilder { /// Creates a new builder pub fn new() -> Self { Self::default() } /// Sets field1 pub fn field1(mut self, value: impl Into<String>) -> Self { self.field1 = Some(value.into()); self } /// Sets field2 pub fn field2(mut self, value: u32) -> Self { self.field2 = Some(value); self } /// Builds the final type pub fn build(self) -> Result<MyType, BuildError> { Ok(MyType { field1: self.field1.ok_or(BuildError::MissingField1)?, field2: self.field2.unwrap_or(42), }) } } /// The main type pub struct MyType { field1: String, field2: u32, } impl MyType { /// Creates a builder for this type pub fn builder() -> MyTypeBuilder { MyTypeBuilder::new() } } #[derive(Debug, thiserror::Error)] pub enum BuildError { #[error("field1 is required")] MissingField1, }
Type-State Pattern
use std::marker::PhantomData; use crate::error::Result; /// Type states pub struct Uninitialized; pub struct Initialized; /// Connection in different states pub struct Connection<State = Uninitialized> { url: String, _state: PhantomData<State>, } impl Connection<Uninitialized> { /// Creates a new uninitialized connection pub fn new(url: impl Into<String>) -> Self { Self { url: url.into(), _state: PhantomData, } } /// Initializes the connection pub fn initialize(self) -> Result<Connection<Initialized>> { // Perform initialization Ok(Connection { url: self.url, _state: PhantomData, }) } } impl Connection<Initialized> { /// Send data (only available when initialized) pub fn send(&self, data: &[u8]) -> Result<()> { // Send implementation Ok(()) } }
Documentation with Rustdoc
Documentation Comments
/// Single-line documentation /// /// Multi-line documentation continues on subsequent lines. /// Use markdown for formatting. /// /// # Examples /// /// ``` /// use my_library::MyType; /// /// let instance = MyType::new("value"); /// assert_eq!(instance.get(), "value"); /// ``` /// /// # Errors /// /// Returns `Err` when: /// - The input is empty /// - The value is invalid /// /// # Panics /// /// Panics if the internal state is corrupted. /// /// # Safety /// /// This function is unsafe because... /// pub struct MyType { value: String, } //! Inner doc comment for modules //! Documents the item that contains it (e.g., in lib.rs or mod.rs) /// Method documentation impl MyType { /// Creates a new instance /// /// # Arguments /// /// * `value` - The initial value /// /// # Examples /// /// ``` /// # use my_library::MyType; /// let instance = MyType::new("test"); /// ``` pub fn new(value: impl Into<String>) -> Self { Self { value: value.into(), } } /// Gets the value #[must_use] pub fn get(&self) -> &str { &self.value } }
Documentation Attributes
// Hide from documentation #[doc(hidden)] pub struct InternalType; // Add documentation link #[doc(alias = "alternative_name")] pub struct MyType; // Inline documentation from another item #[doc(inline)] pub use internal::PublicType; // Conditional documentation (for docs.rs) #[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg(feature = "async")] pub mod async_module { // Async functionality }
Code Examples in Docs
/// Example with hidden setup code /// /// ``` /// # use my_library::MyType; /// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// let instance = MyType::new("value")?; /// assert_eq!(instance.get(), "value"); /// # Ok(()) /// # } /// ``` pub fn example() {} /// No-run example (compiles but doesn't run) /// /// ```no_run /// use my_library::connect; /// let conn = connect("localhost:8080"); /// ``` pub fn no_run_example() {} /// Ignore example (doesn't compile) /// /// ```ignore /// // Pseudo-code /// let x = something_not_real(); /// ``` pub fn ignore_example() {} /// Example that should fail /// /// ```should_panic /// use my_library::MyType; /// MyType::new(""); // Panics on empty string /// ``` pub fn should_panic_example() {} /// Example with compile_fail /// /// ```compile_fail /// use my_library::MyType; /// let x: i32 = MyType::new("test"); // Type error /// ``` pub fn compile_fail_example() {}
Module-Level Documentation
//! # Module Name //! //! This module provides... //! //! ## Overview //! //! Detailed description... //! //! ## Examples //! //! ``` //! use my_library::module::Type; //! ``` // Module contents
Feature Flags
Defining Features
# Cargo.toml [features] # Default features (enabled automatically) default = ["std"] # Feature flag with no dependencies std = [] # Feature flag that enables optional dependencies async = ["tokio", "futures"] # Feature that enables other features full = ["std", "async", "serde"] # Feature that enables dependency features serde = ["dep:serde", "serde/derive"] [dependencies] tokio = { version = "1.0", optional = true } futures = { version = "0.3", optional = true } serde = { version = "1.0", optional = true }
Using Features in Code
// Conditional compilation based on feature #[cfg(feature = "async")] pub mod async_module { use tokio::runtime::Runtime; pub fn async_function() { // Async implementation } } #[cfg(not(feature = "std"))] use core::fmt; #[cfg(feature = "std")] use std::fmt; // Function available only with feature #[cfg(feature = "async")] pub async fn process_async(data: &[u8]) -> Result<()> { // Implementation Ok(()) } // Different implementations based on features #[cfg(feature = "std")] pub fn allocate() -> Vec<u8> { Vec::new() } #[cfg(not(feature = "std"))] pub fn allocate() -> heapless::Vec<u8, 256> { heapless::Vec::new() }
Feature Documentation
#![cfg_attr(docsrs, feature(doc_cfg))] /// This type requires the `async` feature #[cfg(feature = "async")] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] pub struct AsyncType; /// This function requires either `feature1` or `feature2` #[cfg(any(feature = "feature1", feature = "feature2"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "feature1", feature = "feature2"))))] pub fn conditional_function() {}
Semantic Versioning
SemVer Rules
Format:
MAJOR.MINOR.PATCH
- MAJOR: Incompatible API changes
- MINOR: Add functionality (backward compatible)
- PATCH: Bug fixes (backward compatible)
Pre-1.0.0: Different rules apply
: Any change can break compatibility0.0.x
: MINOR acts like MAJOR, PATCH acts like MINOR0.y.z
What Requires a Major Version Bump
// Breaking changes (require MAJOR bump): // 1. Removing public items pub struct OldType; // Removed entirely // 2. Adding trait bounds to public types pub struct Generic<T>; // Changed to: pub struct Generic<T: Clone>; // 3. Changing function signatures pub fn process(x: u32) {} // Changed to: pub fn process(x: u64) {} // 4. Changing trait methods pub trait MyTrait { fn method(&self); // Changed to: fn method(&self) -> i32; } // 5. Removing or renaming public fields pub struct Type { pub field: String, // Removed or renamed } // 6. Changing error types pub fn operation() -> Result<(), OldError> {} // Changed to: pub fn operation() -> Result<(), NewError> {}
What is Compatible (MINOR version)
// Compatible changes (MINOR bump): // 1. Adding new public items pub struct NewType; pub fn new_function() {} // 2. Adding trait implementations impl Clone for ExistingType {} // 3. Adding defaulted type parameters pub struct Type<T = DefaultType>; // 4. Adding private fields to structs (if not constructable) pub struct Type { existing: String, new_private: u32, // OK if struct is #[non_exhaustive] or has no pub constructor } // 5. Making things more public pub(crate) fn internal() {} // Changed to: pub fn internal() {} // 6. Relaxing trait bounds pub fn process<T: Clone + Send>(x: T) {} // Changed to: pub fn process<T: Clone>(x: T) {}
Preventing Breaking Changes
// Use #[non_exhaustive] to reserve right to add fields #[non_exhaustive] pub struct Config { pub field1: String, pub field2: u32, } // Use builder pattern to avoid breaking changes impl Config { pub fn builder() -> ConfigBuilder { ConfigBuilder::default() } } // Use sealed trait pattern to prevent external implementations mod sealed { pub trait Sealed {} } pub trait MyTrait: sealed::Sealed { // Methods } impl sealed::Sealed for MyType {} impl MyTrait for MyType { // Implementation }
Publishing to crates.io
Pre-Publication Checklist
# 1. Verify package builds cargo build --release # 2. Run all tests cargo test --all-features # 3. Check documentation cargo doc --no-deps --all-features --open # 4. Run clippy cargo clippy --all-features -- -D warnings # 5. Format code cargo fmt --check # 6. Verify package contents cargo package --list # 7. Do a dry-run publish cargo publish --dry-run
Required Files
my-library/ ├── Cargo.toml # Must have required metadata ├── README.md # Shown on crates.io ├── LICENSE-MIT # or LICENSE-APACHE or LICENSE ├── CHANGELOG.md # Version history (recommended) └── src/ └── lib.rs # Library code
Cargo.toml Metadata for Publishing
[package] name = "my-library" version = "0.1.0" edition = "2021" authors = ["Your Name <email@example.com>"] license = "MIT OR Apache-2.0" description = "A short description (max 256 chars)" documentation = "https://docs.rs/my-library" homepage = "https://github.com/user/my-library" repository = "https://github.com/user/my-library" readme = "README.md" keywords = ["cli", "tool", "utility"] # Max 5, each max 20 chars categories = ["command-line-utilities"] # From crates.io list exclude = [ "tests/fixtures/*", ".github/*", "*.png", ] [badges] # CI badge github-actions = { repository = "user/repo", workflow = "CI" }
Publishing Workflow
# 1. Create crates.io account and get API token # Visit https://crates.io/me # 2. Login to crates.io cargo login <your-api-token> # 3. Publish the crate cargo publish # 4. Verify publication # Visit https://crates.io/crates/my-library
Yanking Versions
# Yank a published version (prevents new projects from using it) cargo yank --version 0.1.0 # Unyank a version cargo yank --vers 0.1.0 --undo
Publishing Updates
# 1. Update version in Cargo.toml # 2. Update CHANGELOG.md # 3. Commit changes git add Cargo.toml CHANGELOG.md git commit -m "chore: bump version to 0.2.0" # 4. Tag release git tag -a v0.2.0 -m "Release v0.2.0" # 5. Publish cargo publish # 6. Push commits and tags git push origin main --tags
Examples and Tests
Examples Directory
// examples/basic.rs //! Basic usage example use my_library::MyType; fn main() -> Result<(), Box<dyn std::error::Error>> { // Simple example showing basic usage let instance = MyType::new("example")?; println!("Created: {:?}", instance); // Show key functionality let result = instance.process()?; println!("Result: {}", result); Ok(()) }
# Run examples cargo run --example basic cargo run --example advanced --features async
Integration Tests
// tests/integration_test.rs use my_library::MyType; #[test] fn test_basic_usage() { let instance = MyType::new("test").unwrap(); assert_eq!(instance.get(), "test"); } #[test] #[cfg(feature = "async")] fn test_async_feature() { // Test async functionality }
Benchmarks
// benches/benchmark.rs use criterion::{black_box, criterion_group, criterion_main, Criterion}; use my_library::MyType; fn benchmark_operation(c: &mut Criterion) { c.bench_function("my_operation", |b| { let instance = MyType::new("bench"); b.iter(|| { black_box(instance.process()) }); }); } criterion_group!(benches, benchmark_operation); criterion_main!(benches);
# Run benchmarks cargo bench
Error Handling
Library Error Types
use std::fmt; /// Library error type #[derive(Debug)] pub enum Error { /// IO error occurred Io(std::io::Error), /// Invalid input InvalidInput(String), /// Configuration error Config(ConfigError), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Io(e) => write!(f, "IO error: {}", e), Error::InvalidInput(msg) => write!(f, "Invalid input: {}", msg), Error::Config(e) => write!(f, "Config error: {}", e), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Io(e) => Some(e), Error::Config(e) => Some(e), _ => None, } } } // Conversions from other error types impl From<std::io::Error> for Error { fn from(err: std::io::Error) -> Self { Error::Io(err) } } impl From<ConfigError> for Error { fn from(err: ConfigError) -> Self { Error::Config(err) } } /// Result type alias for convenience pub type Result<T> = std::result::Result<T, Error>; #[derive(Debug)] pub struct ConfigError { message: String, } impl fmt::Display for ConfigError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.message) } } impl std::error::Error for ConfigError {}
Using thiserror
// Add to Cargo.toml: // thiserror = "1.0" use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Invalid input: {0}")] InvalidInput(String), #[error("Configuration error")] Config(#[from] ConfigError), #[error("Not found: {0}")] NotFound(String), } pub type Result<T> = std::result::Result<T, Error>;
Troubleshooting
Documentation Build Failures
Problem: Broken doc links
// Error: unresolved link to `NonExistent` /// See [`NonExistent`] for details // Fix: Use correct paths /// See [`crate::actual::Type`] for details /// Or: See [Type](crate::actual::Type)
Problem: Doc tests fail
# Run doc tests to see failures cargo test --doc # Fix: Add necessary imports to doc examples /// ``` /// # use my_library::MyType; # Hidden line /// let x = MyType::new(); /// ```
Publishing Failures
Problem: Version already published
# Error: crate version `0.1.0` is already uploaded # Fix: Bump version in Cargo.toml
Problem: Missing required fields
# Error: missing required field `description` # Fix: Add to Cargo.toml description = "A library that does X" license = "MIT OR Apache-2.0"
Problem: Package too large
# Error: package size exceeds 10 MB limit # Fix: Exclude unnecessary files exclude = [ "tests/fixtures/*.bin", "docs/images/*.png", ".github/", ]
Best Practices
API Design
-
Prefer explicit over implicit
- Clear function names
- Explicit error types
- Documented behavior
-
Use builders for complex construction
- Many optional parameters → builder pattern
- Complex validation → builder with
methodbuild()
-
Provide type safety
- Use type-state pattern where applicable
- Leverage the type system to prevent invalid states
-
Follow naming conventions
for constructorsnew()
for builderswith_*
for consuming conversionsinto_*
for cheap referencesas_*
for expensive conversionsto_*
Versioning
- Follow SemVer strictly
- Maintain a CHANGELOG.md
- Use
for extensible types#[non_exhaustive] - Document MSRV (Minimum Supported Rust Version)
Documentation
- Document all public items
- Include examples in documentation
- Use doc tests to verify examples
- Add crate-level documentation in lib.rs
Testing
- Test public API in integration tests
- Provide examples for common use cases
- Use doc tests for simple examples
- Benchmark performance-critical code
References
See Also
- Rust fundamentals and syntaxlang-rust-dev
- Binary application developmentlang-rust-bin-dev
- Testing strategieslang-rust-testing-dev