Kodunda ลunu gรถrรผyorsan
pub mod internal { pub struct Helper { ... } } leaking implementation details
Default to pub(crate); reserve pub for the contract; #[non_exhaustive] enums and sealed traits keep you semver-stable.
Public APIs and module visibility
Crate design involves structuring Rust libraries and binaries for maintainability, usability, and discoverability. Well-designed crates have clear APIs, good documentation, and sensible module organization.
Poorly designed crates suffer from:
// src/lib.rs - Main entry point
//! # MyAwesomeCrate
//!
//! A library for doing awesome things with Rust.
//!
//! ## Quick Start
//!
//!rust
//! use myawesomecrate::prelude::*;
//!
//! let client = Client::builder()
//! .with_timeout(Duration::from_secs(30))
//! .build()?;
//!
//! let result = client.process("input").await?;
//!
//!
//! ## Features
//!
//! - `async` - Enable async support (default)
//! - `serde` - Enable serialization support
//! - `tracing` - Enable tracing instrumentation
#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
// Re-export main types at crate root for convenience
pub use client::Client;
pub use config::Config;
pub use error::{Error, Result};
// Public modules
pub mod client;
pub mod config;
pub mod error;
// Feature-gated modules
#[cfg(feature = "serde")]
pub mod serialization;
// Internal modules (not pub)
mod internal;
mod utils;
/// Prelude module for convenient imports
pub mod prelude {
pub use crate::client::{Client, ClientBuilder};
pub use crate::config::Config;
pub use crate::error::{Error, Result};
// Re-export commonly used types from dependencies
pub use std::time::Duration;
}
// src/error.rs - Error handling
//! Error types for the crate
use std::fmt;
/// The error type for all operations in this crate
#[derive(Debug)]
pub enum Error {
/// Configuration error
Config(ConfigError),
/// Network error
Network(NetworkError),
/// Timeout error
Timeout {
/// Operation that timed out
operation: &'static str,
/// Timeout duration
duration: std::time::Duration,
},
/// Invalid input
InvalidInput(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Config(e) => write!(f, "configuration error: {}", e),
Error::Network(e) => write!(f, "network error: {}", e),
Error::Timeout { operation, duration } => {
write!(f, "{} timed out after {:?}", operation, duration)
}
Error::InvalidInput(msg) => write!(f, "invalid input: {}", msg),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Config(e) => Some(e),
Error::Network(e) => Some(e),
_ => None,
}
}
}
/// Configuration-specific errors
#[derive(Debug)]
pub enum ConfigError {
/// Missing required field
MissingField(&'static str),
/// Invalid value
InvalidValue { field: &'static str, reason: String },
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigError::MissingField(field) => {
write!(f, "missing required field: {}", field)
}
ConfigError::InvalidValue { field, reason } => {
write!(f, "invalid value for {}: {}", field, reason)
}
}
}
}
impl std::error::Error for ConfigError {}
/// Network-specific errors
#[derive(Debug)]
pub struct NetworkError {
kind: NetworkErrorKind,
message: String,
}
#[derive(Debug)]
enum NetworkErrorKind {
ConnectionFailed,
ConnectionReset,
Dns,
}
impl fmt::Display for NetworkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for NetworkError {}
/// A specialized Result type for this crate
pub type Result<T> = std::result::Result<T, Error>;
// Convenient From implementations
impl From<ConfigError> for Error {
fn from(err: ConfigError) -> Self {
Error::Config(err)
}
}
impl From<NetworkError> for Error {
fn from(err: NetworkError) -> Self {
Error::Network(err)
}
}
// src/config.rs - Configuration with builder pattern
//! Configuration for the client
use crate::error::{ConfigError, Error, Result};
use std::time::Duration;
/// Client configuration
#[derive(Debug, Clone)]
pub struct Config {
pub(crate) endpoint: String,
pub(crate) timeout: Duration,
pub(crate) retries: u32,
pub(crate) headers: Vec<(String, String)>,
}
impl Config {
/// Create a new configuration builder
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}
/// Get the configured endpoint
pub fn endpoint(&self) -> &str {
&self.endpoint
}
/// Get the configured timeout
pub fn timeout(&self) -> Duration {
self.timeout
}
}
impl Default for Config {
fn default() -> Self {
Config {
endpoint: "https://api.example.com".to_string(),
timeout: Duration::from_secs(30),
retries: 3,
headers: Vec::new(),
}
}
}
/// Builder for creating Config instances
#[derive(Debug, Default)]
pub struct ConfigBuilder {
endpoint: Option<String>,
timeout: Option<Duration>,
retries: Option<u32>,
headers: Vec<(String, String)>,
}
impl ConfigBuilder {
/// Create a new builder with default values
pub fn new() -> Self {
ConfigBuilder::default()
}
/// Set the API endpoint
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = Some(endpoint.into());
self
}
/// Set the request timeout
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
/// Set the number of retries
pub fn retries(mut self, retries: u32) -> Self {
self.retries = Some(retries);
self
}
/// Add a custom header
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((name.into(), value.into()));
self
}
/// Build the configuration
pub fn build(self) -> Result<Config> {
let endpoint = self.endpoint
.ok_or(ConfigError::MissingField("endpoint"))?;
// Validate endpoint
if !endpoint.starts_with("http://") && !endpoint.starts_with("https://") {
return Err(ConfigError::InvalidValue {
field: "endpoint",
reason: "must start with http:// or https://".to_string(),
}.into());
}
Ok(Config {
endpoint,
timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
retries: self.retries.unwrap_or(3),
headers: self.headers,
})
}
}
// src/client.rs - Main client with good API design
//! Client for interacting with the service
use crate::config::Config;
use crate::error::{Error, Result};
use std::time::Duration;
/// The main client for interacting with the service
///
/// # Example
///
///rust,no_run
/// use myawesomecrate::{Client, Config};
/// use std::time::Duration;
///
/// # async fn example() -> myawesomecrate::Result<()> {
/// let client = Client::builder()
/// .endpoint("https://api.example.com")
/// .timeout(Duration::from_secs(30))
/// .build()?;
///
/// let response = client.get("/users").await?;
/// # Ok(())
/// # }
///
#[derive(Debug)]
pub struct Client {
config: Config,
}
impl Client {
/// Create a new client with the given configuration
pub fn new(config: Config) -> Self {
Client { config }
}
/// Create a client builder
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
/// Perform a GET request
pub async fn get(&self, path: &str) -> Result<Response> {
self.request(Method::Get, path, None).await
}
/// Perform a POST request
pub async fn post(&self, path: &str, body: impl Into<Body>) -> Result<Response> {
self.request(Method::Post, path, Some(body.into())).await
}
/// Perform a PUT request
pub async fn put(&self, path: &str, body: impl Into<Body>) -> Result<Response> {
self.request(Method::Put, path, Some(body.into())).await
}
/// Perform a DELETE request
pub async fn delete(&self, path: &str) -> Result<Response> {
self.request(Method::Delete, path, None).await
}
async fn request(
&self,
method: Method,
path: &str,
body: Option<Body>,
) -> Result<Response> {
// Implementation would go here
let _ = (method, path, body);
Ok(Response {
status: 200,
body: vec![],
})
}
}
/// Builder for creating Client instances
#[derive(Debug, Default)]
pub struct ClientBuilder {
endpoint: Option<String>,
timeout: Option<Duration>,
retries: Option<u32>,
headers: Vec<(String, String)>,
}
impl ClientBuilder {
/// Create a new client builder
pub fn new() -> Self {
ClientBuilder::default()
}
/// Set the API endpoint
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = Some(endpoint.into());
self
}
/// Set the request timeout
///
/// Alias for `with_timeout` for API ergonomics
pub fn timeout(self, timeout: Duration) -> Self {
self.with_timeout(timeout)
}
/// Set the request timeout (alternative name)
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
/// Set the number of retries
pub fn retries(mut self, retries: u32) -> Self {
self.retries = Some(retries);
self
}
/// Add a custom header
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((name.into(), value.into()));
self
}
/// Build the client
pub fn build(self) -> Result<Client> {
let config = Config::builder()
.endpoint(self.endpoint.unwrap_or_else(|| "https://api.example.com".to_string()))
.timeout(self.timeout.unwrap_or(Duration::from_secs(30)))
.retries(self.retries.unwrap_or(3));
let config = self.headers.into_iter()
.fold(config, |c, (k, v)| c.header(k, v))
.build()?;
Ok(Client::new(config))
}
}
// Supporting types
#[derive(Debug, Clone, Copy)]
enum Method {
Get,
Post,
Put,
Delete,
}
/// Request body
#[derive(Debug)]
pub struct Body(Vec<u8>);
impl From<&str> for Body {
fn from(s: &str) -> Self {
Body(s.as_bytes().to_vec())
}
}
impl From<String> for Body {
fn from(s: String) -> Self {
Body(s.into_bytes())
}
}
impl From<Vec<u8>> for Body {
fn from(v: Vec<u8>) -> Self {
Body(v)
}
}
/// Response from the API
#[derive(Debug)]
pub struct Response {
/// HTTP status code
pub status: u16,
/// Response body
pub body: Vec<u8>,
}
impl Response {
/// Check if the response was successful (2xx)
pub fn is_success(&self) -> bool {
(200..300).contains(&self.status)
}
/// Get the body as a string
pub fn text(&self) -> Result<String> {
String::from_utf8(self.body.clone())
.map_err(|_| Error::InvalidInput("Response is not valid UTF-8".to_string()))
}
/// Deserialize the body as JSON
#[cfg(feature = "serde")]
pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
serde_json::from_slice(&self.body)
.map_err(|e| Error::InvalidInput(e.to_string()))
}
}
# Cargo.toml - Feature organization
[package]
name = "myawesomecrate"
version = "1.0.0"
edition = "2021"
description = "A library for doing awesome things"
documentation = "https://docs.rs/myawesomecrate"
repository = "https://github.com/example/myawesomecrate"
license = "MIT OR Apache-2.0"
keywords = ["awesome", "rust", "example"]
categories = ["development-tools"]
[features]
default = ["async"]
async = ["tokio"]
serde = ["dep:serde", "dep:serde_json"]
tracing = ["dep:tracing"]
full = ["async", "serde", "tracing"]
[dependencies]
tokio = { version = "1.35", features = ["rt", "time"], optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }
tracing = { version = "0.1", optional = true }
[dev-dependencies]
tokio = { version = "1.35", features = ["full", "test-util"] }
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
lib.rs shows what's publicsrc/
โโโ lib.rs # Crate root, re-exports
โโโ prelude.rs # Convenient imports
โโโ error.rs # Error types
โโโ config.rs # Configuration
โโโ client.rs # Main functionality
โโโ internal/ # Private implementation
โ โโโ mod.rs
โ โโโ helpers.rs
โโโ tests/ # Integration tests
| Principle | Example |
|-----------|---------|
| Sensible defaults | Config::default() works |
| Progressive disclosure | Basic usage is simple, advanced options available |
| Consistent naming | with_ or set_, not mixed |
| Type safety | Newtypes for domain concepts |
| Fail fast | Validation in builders |
#[must_use] to appropriate methodsDebug that hides sensitive fieldsRun this code in the official Rust Playground
Missing field in struct literal
Name is imported twice
Cannot find type in this scope
Cannot find value in this scope
Unresolved import
Failed to resolve
An extern crate loading macros must be at the crate root
Struct field doesn't exist
Expected type, found ...
Module/item is private