# Try Create
[](https://crates.io/crates/try_create) <!-- Replace with your actual crate name if different -->
[](https://docs.rs/try_create) <!-- Replace with your actual crate name if different -->
`try_create` is a small Rust utility library that provides generic traits for object creation, offering standardized ways to handle infallible, fallible, conditional, and policy-based validated construction.
## Overview
This library introduces a set of traits to streamline the creation of new type instances:
* **`IntoInner`**: A re-exported trait from the [`into_inner`](https://crates.io/crates/into_inner) crate, used as a supertrait to define the input type for construction.
* **`TryNew`**: For fallible construction, where creating an instance might fail (e.g., due to validation rules). It returns a `Result`.
* **`New`**: For infallible construction. If an invariant is violated, this method is expected to panic.
* **`ConditionallyCreate`**: A utility trait that switches creation logic based on the build profile:
* In **debug** mode, it uses `TryNew::try_new().expect()`, panicking if `try_new` fails.
* In **release** mode, it uses `New::new()`.
* **`ValidationPolicy`**: Defines a contract for validation logic. A policy specifies how a value should be validated and what error type is returned upon failure. This allows for reusable validation strategies.
* **`TryNewValidated`**: Extends `TryNew` by associating a specific `ValidationPolicy` with the type. The `TryNewValidated::try_new_validated` method first applies the policy and then, if successful, proceeds with the underlying `TryNew` construction logic.
These traits are designed to be general-purpose and can be used for various types, promoting a consistent API for object instantiation. The library supports `no_std` environments.
## Installation
Add `try_create` to your `Cargo.toml`:
```toml
[dependencies]
try_create = "0.1" # Replace with the latest version
into_inner = "0.1" # try_create re-exports IntoInner, but you might depend on it directly too
```
## Usage
### 1. `IntoInner` Trait
Both `TryNew` and `New` require `IntoInner` to be implemented. This trait defines the `InnerType` that your constructor will accept and a way to retrieve this inner value from an instance.
```rust
use try_create::IntoInner;
#[derive(Debug, PartialEq)]
struct MyValueContainer {
value: i32,
}
impl IntoInner for MyValueContainer {
type InnerType = i32;
fn into_inner(self) -> Self::InnerType {
self.value
}
}
```
### 2. `TryNew` Trait (Fallible Creation)
Use `TryNew` when the creation process can fail and you want to return a `Result`.
```rust
use try_create::{TryNew, IntoInner};
#[cfg(feature = "std")]
use std::fmt;
#[cfg(feature = "std")]
use std::error::Error;
// Define a custom error type
#[derive(Debug, PartialEq)]
struct NotPositiveError;
#[cfg(feature = "std")]
impl fmt::Display for NotPositiveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Value must be positive")
}
}
#[cfg(feature = "std")]
impl Error for NotPositiveError {}
// For no_std, NotPositiveError would just need to implement core::fmt::Debug,
// which it does via #[derive(Debug, PartialEq)].
// A struct that wraps an i32, ensuring it's positive.
#[derive(Debug, PartialEq)]
struct PositiveInteger {
value: i32,
}
impl IntoInner for PositiveInteger {
type InnerType = i32;
fn into_inner(self) -> Self::InnerType { self.value }
}
impl TryNew for PositiveInteger {
type Error = NotPositiveError;
// `InnerType` is `i32`, inherited from `IntoInner`.
fn try_new(value: Self::InnerType) -> Result<Self, Self::Error> {
if value > 0 {
Ok(PositiveInteger { value })
} else {
Err(NotPositiveError)
}
}
}
// Usage
assert_eq!(PositiveInteger::try_new(10), Ok(PositiveInteger { value: 10 }));
assert_eq!(PositiveInteger::try_new(0), Err(NotPositiveError));
assert_eq!(PositiveInteger::try_new(-5), Err(NotPositiveError));
let positive_num = PositiveInteger::try_new(42).unwrap();
assert_eq!(positive_num.into_inner(), 42);
```
### 3. `New` Trait (Infallible/Panicking Creation)
Use `New` when the creation process should not fail in a recoverable way. If invariants are violated, `New::new` should panic.
```rust
use try_create::{New, IntoInner, TryNew}; // TryNew often used to implement New
#[cfg(feature = "std")]
use std::fmt;
#[cfg(feature = "std")]
use std::error::Error;
// Example struct
#[derive(Debug, PartialEq)]
struct MyType(i32);
// Custom error for TryNew implementation (if used to implement New)
#[derive(Debug, PartialEq)]
struct MyTypeError(String);
#[cfg(feature = "std")]
impl fmt::Display for MyTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MyTypeError: {}", self.0)
}
}
#[cfg(feature = "std")]
impl Error for MyTypeError {}
impl IntoInner for MyType {
type InnerType = i32;
fn into_inner(self) -> Self::InnerType { self.0 }
}
// Optional TryNew, can be used to implement New
impl TryNew for MyType {
type Error = MyTypeError;
fn try_new(value: i32) -> Result<Self, Self::Error> {
if value < 0 { Err(MyTypeError("Value cannot be negative".to_string())) }
else { Ok(MyType(value)) }
}
}
impl New for MyType {
fn new(value: i32) -> Self {
// Example: using try_new and panicking on error
// match Self::try_new(value) {
// Ok(instance) => instance,
// Err(e) => panic!("MyType::new failed: {:?}", e),
// }
// Or direct implementation:
if value < 0 {
panic!("MyType::new: Value cannot be negative");
}
MyType(value)
}
}
// Usage
assert_eq!(MyType::new(10), MyType(10));
// The following would panic:
// MyType::new(-5);
```
### 4. `ConditionallyCreate` Trait
This trait provides a `create_conditionally` method that behaves differently in debug and release builds.
```rust
use try_create::{ConditionallyCreate, TryNew, New, IntoInner};
#[cfg(feature = "std")]
use std::fmt;
#[cfg(feature = "std")]
use std::error::Error;
// Using PositiveInteger and NotPositiveError from the TryNew example above
#[derive(Debug, PartialEq)]
struct NotPositiveError;
#[cfg(feature = "std")]
impl fmt::Display for NotPositiveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Value must be positive")
}
}
#[cfg(feature = "std")]
impl Error for NotPositiveError {}
#[derive(Debug, PartialEq)]
struct PositiveInteger { value: i32 }
impl IntoInner for PositiveInteger {
type InnerType = i32;
fn into_inner(self) -> Self::InnerType {
self.value
}
}
impl TryNew for PositiveInteger {
type Error = NotPositiveError;
fn try_new(value: Self::InnerType) -> Result<Self, Self::Error> {
if value > 0 { Ok(PositiveInteger { value }) } else { Err(NotPositiveError) }
}
}
// PositiveInteger must also implement New to use ConditionallyCreate
impl New for PositiveInteger {
fn new(value: Self::InnerType) -> Self {
match Self::try_new(value) {
Ok(instance) => instance,
Err(e) => panic!("PositiveInteger::new failed for a non-positive value. Error: {:?}", e),
}
}
}
// Usage of ConditionallyCreate
let p1 = PositiveInteger::create_conditionally(10);
assert_eq!(p1, PositiveInteger { value: 10 });
// If PositiveInteger::create_conditionally(-5) was called:
// - In debug mode: it would panic with "ConditionallyCreate: try_new() failed in debug mode".
// - In release mode: it would panic with "PositiveInteger::new failed...".
```
### 5. `ValidationPolicy` Trait
Defines a reusable validation strategy.
```rust
use try_create::ValidationPolicy;
#[cfg(feature = "std")]
use std::fmt;
#[cfg(feature = "std")]
use std::error::Error;
#[derive(Debug, PartialEq)]
struct MinValueError(String);
#[cfg(feature = "std")]
impl fmt::Display for MinValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MinValueError: {}", self.0)
}
}
#[cfg(feature = "std")]
impl Error for MinValueError {}
struct MinValuePolicy {
min_value: i32,
}
impl ValidationPolicy for MinValuePolicy {
type Value = i32;
type Error = MinValueError;
// `validate` (consuming) has a default implementation that calls `validate_ref`.
// We only need to implement `validate_ref`.
fn validate_ref(v: &Self::Value) -> Result<(), Self::Error> {
// For this example, we'll imagine the policy has a configured minimum.
// In a real scenario, MinValuePolicy might store `min_value` or it could be a const.
// Let's assume a fixed minimum for simplicity in this example.
const EXAMPLE_MIN: i32 = 0;
if *v >= EXAMPLE_MIN {
Ok(())
} else {
Err(MinValueError(format!("Value {} is less than minimum {}", v, EXAMPLE_MIN)))
}
}
}
// Usage
assert_eq!(MinValuePolicy::validate_ref(&5), Ok(()));
assert!(MinValuePolicy::validate_ref(&-1).is_err());
assert_eq!(MinValuePolicy::validate(10), Ok(10)); // Uses default validate
assert!(MinValuePolicy::validate(-5).is_err()); // Uses default validate
```
### 6. `TryNewValidated` Trait
Combines a `ValidationPolicy` with `TryNew` for two-phase construction.
```rust
use try_create::{TryNewValidated, ValidationPolicy, TryNew, IntoInner};
#[cfg(feature = "std")]
use std::fmt;
#[cfg(feature = "std")]
use std::error::Error;
// --- Reusing MinValuePolicy and MinValueError from previous example ---
#[derive(Debug, PartialEq)]
struct MinValueError(String);
#[cfg(feature = "std")]
impl fmt::Display for MinValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MinValueError: {}", self.0)
}
}
#[cfg(feature = "std")]
impl Error for MinValueError {}
struct MinValuePolicy; // Simplified for example, assumes fixed min of 0
impl ValidationPolicy for MinValuePolicy {
type Value = i32; type Error = MinValueError;
fn validate_ref(v: &Self::Value) -> Result<(), Self::Error> {
if *v >= 0 { Ok(()) } else { Err(MinValueError("Value must be non-negative".to_string())) }
}
}
// --- Define the type to be created and its specific errors ---
#[derive(Debug, PartialEq)]
enum ValidatedNumberError {
Policy(MinValueError), // Error from the validation policy
Construction(String), // Error from the TryNew part
}
// Implement From for error conversion (Policy::Error -> Self::Error)
impl From<MinValueError> for ValidatedNumberError {
fn from(e: MinValueError) -> Self {
ValidatedNumberError::Policy(e)
}
}
#[cfg(feature = "std")]
impl fmt::Display for ValidatedNumberError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValidatedNumberError::Policy(e) => write!(f, "Policy validation failed: {}", e),
ValidatedNumberError::Construction(s) => write!(f, "Construction failed: {}", s),
}
}
}
#[cfg(feature = "std")]
impl Error for ValidatedNumberError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ValidatedNumberError::Policy(e) => Some(e),
ValidatedNumberError::Construction(_) => None,
}
}
}
#[derive(Debug, PartialEq)]
struct ValidatedNumber(i32);
impl IntoInner for ValidatedNumber {
type InnerType = i32;
fn into_inner(self) -> Self::InnerType { self.0 }
}
// 1. Implement TryNew for the core construction logic (post-validation)
impl TryNew for ValidatedNumber {
type Error = ValidatedNumberError; // Uses the combined error type
fn try_new(value: Self::InnerType) -> Result<Self, Self::Error> {
// Assume after policy validation, we have another rule: value must be even.
if value % 2 == 0 {
Ok(ValidatedNumber(value))
} else {
Err(ValidatedNumberError::Construction(format!("Value {} is not even", value)))
}
}
}
// 2. Implement TryNewValidated, associating the policy
impl TryNewValidated for ValidatedNumber {
type Policy = MinValuePolicy; // Specify which policy to use
// `InnerType` is inherited from `IntoInner`.
// `Error` is inherited from `TryNew`.
}
// Usage
// Policy: value >= 0. Construction: value must be even.
// Valid: 10 >= 0 (policy pass), 10 is even (construction pass)
assert_eq!(ValidatedNumber::try_new_validated(10), Ok(ValidatedNumber(10)));
// Invalid: Policy fails (-5 < 0)
let err_policy = ValidatedNumber::try_new_validated(-5).unwrap_err();
match err_policy {
ValidatedNumberError::Policy(MinValueError(msg)) => assert!(msg.contains("Value must be non-negative")),
_ => panic!("Expected policy error"),
}
// Invalid: Policy passes (5 >= 0), but construction fails (5 is not even)
let err_constr = ValidatedNumber::try_new_validated(5).unwrap_err();
match err_constr {
ValidatedNumberError::Construction(msg)) => assert!(msg.contains("Value 5 is not even")),
_ => panic!("Expected construction error"),
}
```
## `no_std` Support
The library is `no_std` compatible.
When the `std` feature is not enabled (default for `no_std` environments):
* `TryNew::Error` and `ValidationPolicy::Error` only require `core::fmt::Debug`.
* You are responsible for defining error types that conform to this.
## Contributing
Contributions, issues, and feature requests are welcome!
## License
This project is licensed under the terms of the MIT license and the Apache License (Version 2.0). See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE) for details. (You'll need to add these license files to your project).