원클릭으로
library-errors
Modeling, exposing, and formatting errors in Rust libraries
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Modeling, exposing, and formatting errors in Rust libraries
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Choosing and profiling the global allocator (jemalloc, mimalloc, system) in the backend-service `service` template
Request lifecycle, Tower layer ordering, and scaling choices in the backend-service `service` template
Observability wiring for the backend-service `service` template: metrique wide-event metrics, tracing logs, and optional dial9 runtime profiling
Setting up automated crate releases with release-plz and OIDC trusted publishing
Standard GitHub Actions CI structure for Rust projects using ci-battery-pack
Cross-platform binary builds for GitHub Releases with cargo-binstall support
| name | library-errors |
| description | Modeling, exposing, and formatting errors in Rust libraries |
How to model errors inside a crate, make them opaque at public boundaries, and control how they render.
Prerequisites: This skill references examples from the error battery pack.
cargo install cargo-bp # install the battery-pack CLI cargo bp add error # add anyhow + thiserror to your project
Within your crate boundary, use thiserror freely. No semver concerns since consumers never see these types.
use thiserror::Error;
#[derive(Error, Debug)]
pub(crate) enum StorageError {
#[error("failed to read from disk")]
Io(#[from] std::io::Error),
#[error("corrupt data at offset {offset}")]
Corrupt { offset: u64 },
#[error("item not found: {0}")]
NotFound(String),
}
Guidelines for internal errors:
#[from] liberally. It generates From impls so ? works across internal boundaries.#[non_exhaustive] needed. You own all the match arms.// in src/indexer.rs
#[derive(Error, Debug)]
pub(crate) enum IndexError {
#[error("storage failure")]
Storage(#[from] StorageError),
#[error("index corrupted")]
Corrupted,
}
At the crate's public API, wrap internal errors in an opaque type. This decouples your internal structure from your consumers' code.
This is the same pattern used by std::io::Error. Private fields, #[non_exhaustive] kind enum, boxed cause:
use std::fmt::Display;
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MyLibErrorKind {
QueueFull,
Storage,
NotFound,
}
#[derive(Debug)]
pub struct MyLibError {
kind: MyLibErrorKind,
cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}
impl MyLibError {
pub fn kind(&self) -> MyLibErrorKind {
self.kind
}
pub(crate) fn new(kind: MyLibErrorKind, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
Self { kind, cause: Some(Box::new(cause)) }
}
pub(crate) fn from_kind(kind: MyLibErrorKind) -> Self {
Self { kind, cause: None }
}
}
impl Display for MyLibError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
MyLibErrorKind::QueueFull => write!(f, "queue is full"),
MyLibErrorKind::Storage => write!(f, "storage operation failed"),
MyLibErrorKind::NotFound => write!(f, "not found"),
}
}
}
impl std::error::Error for MyLibError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.cause.as_deref()
}
}
This gives semver freedom: private fields hide internals, #[non_exhaustive] allows adding variants, and the boxed cause hides dependency types. Copy on the kind lets .kind() return by value; if you later need variants carrying data, derive only Clone and return &MyLibErrorKind instead.
impl From<StorageError> for MyLibError {
fn from(e: StorageError) -> Self {
let kind = match &e {
StorageError::NotFound(_) => MyLibErrorKind::NotFound,
_ => MyLibErrorKind::Storage,
};
MyLibError::new(kind, e)
}
}
Map your rich internal taxonomy to the coarser public kinds. The internal error becomes the opaque .source().
thiserror with boxed sources gives derive convenience without exposing dependency types:
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum MyLibError {
#[error("queue is full")]
QueueFull,
#[error("storage operation failed")]
Storage { #[source] source: Box<dyn std::error::Error + Send + Sync + 'static> },
#[error("not found")]
NotFound,
}
Use this only for crates with stable, well-understood error categories. For evolving APIs, prefer the struct pattern: any public enum has a larger semver surface than a struct (variant names, field arity, and tuple positions are all public API).
match client.upload(data) {
Ok(()) => {}
Err(e) => match e.kind() {
MyLibErrorKind::QueueFull => retry_later(),
MyLibErrorKind::NotFound => create_and_retry(),
_ => return Err(e.into()),
}
}
Display must not include the source. Reporters walk .source() separately:
// Wrong: source appears twice in output
write!(f, "storage failed: {}", self.cause.as_ref().unwrap())
// Right
write!(f, "storage failed")
// Good: composes well when reporters join with ": "
"connection refused"
"invalid header: expected utf-8"
// Bad
"Connection refused."
"Error: Invalid header"
If consumers need to downcast to your dependency's error type, re-export it:
pub use aws_sdk_s3::error::SdkError;
Prefer exposing information through your error kind or accessor methods over requiring consumers to downcast.
thiserror enum per module, #[from] for conversions#[non_exhaustive], private fields, accessor methodsFrom impls map internal errors to public error kindsBox<dyn Error + Send + Sync + 'static> for causes (not concrete types)Display prints only this error's message, never the sourcesource() returns the underlying causeSee the application-errors skill for the consumer side: propagating errors with anyhow, formatting for logs and terminals, backtrace configuration.
cargo run --example custom-errors -p error-battery-pack