| name | lang-rust-library-dev |
| description | 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. |
Rust 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 | cargo new mylib --lib |
| Build library | cargo build --release |
| Run library tests | cargo test |
| Generate docs | cargo doc --open |
| Check API docs | cargo doc --no-deps --open |
| Publish to crates.io | cargo publish |
| Feature flag | #[cfg(feature = "my-feature")] |
| Public re-export | pub use internal::Type; |
| Doc example | /// # Examples |
| Hide from docs | #[doc(hidden)] |
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 (lang-rust-library-dev) |
| Build CLI or binary applications | lang-rust-bin-dev |
| Set up testing, mocking, property tests | lang-rust-testing-dev |
| Work with async/await, tokio, futures | lang-rust-async-dev |
| Learn Rust syntax and fundamentals | lang-rust-dev |
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"
[lib]
name = "my_library"
path = "src/lib.rs"
crate-type = ["lib"]
[dependencies]
serde = "1.0"
tokio = { version = "1.0", optional = true }
[dev-dependencies]
criterion = "0.5"
proptest = "1.0"
[features]
default = ["std"]
std = []
async = ["tokio", "tokio/rt-multi-thread"]
serde = ["dep:serde", "serde/derive"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
Dependency Specifications
[dependencies]
exact = "=1.2.3"
caret = "^1.2.3"
tilde = "~1.2.3"
wildcard = "1.*"
range = ">=1.2, <1.5"
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" }
my-local-crate = { path = "../my-local-crate" }
optional-dep = { version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"], default-features = false }
Library Types
[lib]
crate-type = ["lib"]
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
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![deny(unsafe_code)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod config;
pub mod error;
mod internal;
pub use error::{Error, Result};
pub use config::Config;
pub mod prelude {
pub use crate::{Config, Error, Result};
pub use crate::something::Something;
}
Module Organization
pub struct PublicStruct {
pub field: String,
private_field: u32,
}
impl PublicStruct {
pub fn new(field: String) -> Self {
Self {
field,
private_field: 0,
}
}
}
fn internal_helper() -> u32 {
42
}
mod sub1;
mod sub2;
pub use sub1::PublicType1;
pub use sub2::PublicType2;
pub struct ModuleStruct;
Public API Design
Visibility and Re-exports
pub mod high_level {
pub use crate::internal::core::CoreType;
pub use crate::internal::utils::UtilType;
}
mod internal {
pub(crate) mod core {
pub struct CoreType;
}
pub(crate) mod utils {
pub struct UtilType;
}
}
Prelude Pattern
pub use crate::error::{Error, Result};
pub use crate::config::Config;
pub use crate::builder::Builder;
pub use crate::traits::{MyTrait, AnotherTrait};
Builder Pattern
#[derive(Debug, Default)]
pub struct MyTypeBuilder {
field1: Option<String>,
field2: Option<u32>,
}
impl MyTypeBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn field1(mut self, value: impl Into<String>) -> Self {
self.field1 = Some(value.into());
self
}
pub fn field2(mut self, value: u32) -> Self {
self.field2 = Some(value);
self
}
pub fn build(self) -> Result<MyType, BuildError> {
Ok(MyType {
field1: self.field1.ok_or(BuildError::MissingField1)?,
field2: self.field2.unwrap_or(42),
})
}
}
pub struct MyType {
field1: String,
field2: u32,
}
impl MyType {
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;
pub struct Uninitialized;
pub struct Initialized;
pub struct Connection<State = Uninitialized> {
url: String,
_state: PhantomData<State>,
}
impl Connection<Uninitialized> {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
_state: PhantomData,
}
}
pub fn initialize(self) -> Result<Connection<Initialized>> {
Ok(Connection {
url: self.url,
_state: PhantomData,
})
}
}
impl Connection<Initialized> {
pub fn send(&self, data: &[u8]) -> Result<()> {
Ok(())
}
}
Documentation with Rustdoc
Documentation Comments
pub struct MyType {
value: String,
}
impl MyType {
pub fn new(value: impl Into<String>) -> Self {
Self {
value: value.into(),
}
}
#[must_use]
pub fn get(&self) -> &str {
&self.value
}
}
Documentation Attributes
#[doc(hidden)]
pub struct InternalType;
#[doc(alias = "alternative_name")]
pub struct MyType;
#[doc(inline)]
pub use internal::PublicType;
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[cfg(feature = "async")]
pub mod async_module {
}
Code Examples in Docs
pub fn example() {}
pub fn no_run_example() {}
pub fn ignore_example() {}
pub fn should_panic_example() {}
pub fn compile_fail_example() {}
Module-Level Documentation
Feature Flags
Defining Features
[features]
default = ["std"]
std = []
async = ["tokio", "futures"]
full = ["std", "async", "serde"]
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
#[cfg(feature = "async")]
pub mod async_module {
use tokio::runtime::Runtime;
pub fn async_function() {
}
}
#[cfg(not(feature = "std"))]
use core::fmt;
#[cfg(feature = "std")]
use std::fmt;
#[cfg(feature = "async")]
pub async fn process_async(data: &[u8]) -> Result<()> {
Ok(())
}
#[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))]
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub struct AsyncType;
#[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
0.0.x: Any change can break compatibility
0.y.z: MINOR acts like MAJOR, PATCH acts like MINOR
What Requires a Major Version Bump
pub struct OldType;
pub struct Generic<T>;
pub struct Generic<T: Clone>;
pub fn process(x: u32) {}
pub fn process(x: u64) {}
pub trait MyTrait {
fn method(&self);
fn method(&self) -> i32;
}
pub struct Type {
pub field: String,
}
pub fn operation() -> Result<(), OldError> {}
pub fn operation() -> Result<(), NewError> {}
What is Compatible (MINOR version)
pub struct NewType;
pub fn new_function() {}
impl Clone for ExistingType {}
pub struct Type<T = DefaultType>;
pub struct Type {
existing: String,
new_private: u32,
}
pub(crate) fn internal() {}
pub fn internal() {}
pub fn process<T: Clone + Send>(x: T) {}
pub fn process<T: Clone>(x: T) {}
Preventing Breaking Changes
#[non_exhaustive]
pub struct Config {
pub field1: String,
pub field2: u32,
}
impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
}
mod sealed {
pub trait Sealed {}
}
pub trait MyTrait: sealed::Sealed {
}
impl sealed::Sealed for MyType {}
impl MyTrait for MyType {
}
Publishing to crates.io
Pre-Publication Checklist
cargo build --release
cargo test --all-features
cargo doc --no-deps --all-features --open
cargo clippy --all-features -- -D warnings
cargo fmt --check
cargo package --list
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"]
categories = ["command-line-utilities"]
exclude = [
"tests/fixtures/*",
".github/*",
"*.png",
]
[badges]
github-actions = { repository = "user/repo", workflow = "CI" }
Publishing Workflow
cargo login <your-api-token>
cargo publish
Yanking Versions
cargo yank --version 0.1.0
cargo yank --vers 0.1.0 --undo
Publishing Updates
git add Cargo.toml CHANGELOG.md
git commit -m "chore: bump version to 0.2.0"
git tag -a v0.2.0 -m "Release v0.2.0"
cargo publish
git push origin main --tags
Examples and Tests
Examples Directory
use my_library::MyType;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let instance = MyType::new("example")?;
println!("Created: {:?}", instance);
let result = instance.process()?;
println!("Result: {}", result);
Ok(())
}
cargo run --example basic
cargo run --example advanced --features async
Integration Tests
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() {
}
Benchmarks
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);
cargo bench
Error Handling
Library Error Types
use std::fmt;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
InvalidInput(String),
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,
}
}
}
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)
}
}
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
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
Problem: Doc tests fail
cargo test --doc
/// ```
///
/// let x = MyType::new();
/// ```
Publishing Failures
Problem: Version already published
Problem: Missing required fields
description = "A library that does X"
license = "MIT OR Apache-2.0"
Problem: Package too large
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
build() method
-
Provide type safety
- Use type-state pattern where applicable
- Leverage the type system to prevent invalid states
-
Follow naming conventions
new() for constructors
with_* for builders
into_* for consuming conversions
as_* for cheap references
to_* for expensive conversions
Versioning
- Follow SemVer strictly
- Maintain a CHANGELOG.md
- Use
#[non_exhaustive] for extensible types
- 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
lang-rust-dev - Rust fundamentals and syntax
lang-rust-bin-dev - Binary application development
lang-rust-testing-dev - Testing strategies