| Crates.io | service-builder |
| lib.rs | service-builder |
| version | 0.3.0 |
| created_at | 2024-12-17 14:10:51.544253+00 |
| updated_at | 2025-08-16 19:36:37.060243+00 |
| description | A lightweight, type-safe service construction library for Rust that provides compile-time dependency injection through builder pattern |
| homepage | |
| repository | https://github.com/krcpa/service-builder |
| max_upload_size | |
| id | 1486322 |
| size | 42,557 |
A lightweight, type-safe service construction library for Rust that leverages the builder pattern to provide a more idiomatic alternative to traditional dependency injection.
Traditional dependency injection frameworks often struggle with Rust's ownership system. The builder pattern works naturally with Rust's ownership rules:
// ❌ Traditional DI approach - fights with ownership
container.register::<UserService>(UserService::new);
let service = container.resolve::<UserService>().unwrap(); // Runtime checks
// ✅ Builder pattern - works with ownership
let service = UserService::builder()
.repository(repo)
.cache(cache)
.build()?; // Compile-time checks
Rust's type system can catch dependency issues at compile time with the builder pattern:
#[builder]
struct UserService {
repository: Arc<dyn Repository>,
cache: Arc<dyn Cache>,
}
// Won't compile if you forget a dependency
let service = UserService::builder()
.repository(repo)
// Forgot .cache()
.build(); // Compile error!
Dependencies are explicit and visible in the code:
let auth_service = AuthService::builder()
.user_repository(user_repo)
.token_service(token_service)
.build()?;
let post_service = PostService::builder()
.post_repository(post_repo)
.auth_service(auth_service) // Clear dependency chain
.build()?;
Add this to your Cargo.toml:
[dependencies]
service-builder = "0.3.0"
use service_builder::prelude::*;
use std::sync::Arc;
#[builder]
struct UserService {
repository: Arc<dyn UserRepository>,
cache: Arc<dyn Cache>,
}
let user_service = UserService::builder()
.repository(user_repo)
.cache(cache)
.build()?;
You can add getter and setter methods to your fields using attributes:
#[builder]
struct Config {
#[builder(getter)] // Generates get_api_key()
api_key: String,
#[builder(setter)] // Generates set_timeout()
timeout: Duration,
#[builder(getter, setter)] // Generates both
max_retries: u32,
}
let mut config = Config::builder()
.api_key("secret".to_string())
.timeout(Duration::from_secs(30))
.max_retries(3)
.build()?;
// Use generated getter
assert_eq!(config.get_api_key(), &"secret".to_string());
// Use generated setter
config.set_max_retries(5);
service-builder now supports configuration patterns with default values:
use std::time::Duration;
#[builder]
struct DatabaseConfig {
// Required field - no default
connection_string: String,
// Optional with custom default
#[builder(default = "Duration::from_secs(30)")]
timeout: Duration,
// Optional with Default trait
#[builder(default)]
ssl_enabled: bool,
// Optional field - defaults to None
#[builder(optional)]
max_connections: Option<usize>,
}
// Use build_with_defaults() for configs
let config = DatabaseConfig::builder()
.connection_string("postgres://localhost/mydb".to_string())
.build_with_defaults()?; // Uses defaults for missing fields
// Or override specific defaults
let config = DatabaseConfig::builder()
.connection_string("postgres://localhost/mydb".to_string())
.timeout(Duration::from_secs(60))
.ssl_enabled(true)
.build_with_defaults()?;
// Strict build() still requires all non-default fields
let config = DatabaseConfig::builder()
.connection_string("postgres://localhost/mydb".to_string())
.build()?; // Works because other fields have defaults
#[builder]
struct AppServices {
#[builder(getter)] // Access services via getters
user_service: Arc<UserService>,
post_service: Arc<PostService>,
}
let app_services = AppServices::builder()
.user_service(Arc::new(user_service))
.post_service(Arc::new(post_service))
.build()?;
// Access services using generated getters
let user_service = app_services.get_user_service();
#[builder(getter)] - Generates a getter method get_field_name() -> &FieldType#[builder(setter)] - Generates a setter method set_field_name(value: FieldType)#[builder(getter, setter)] - Generates both getter and setter methods#[builder(default)] - Field uses Default::default() if not provided#[builder(default = "expression")] - Field uses custom default expression#[builder(optional)] - For Option<T> fields, defaults to Nonebuild() - Strict build, returns error if required fields are missingbuild_with_defaults() - Permissive build, uses defaults where availableWe welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE file for details.