# num-valid Architecture Guide
**Version**: 0.3.2
**Last Updated**: January 12, 2026
**Audience**: Contributors, maintainers, and advanced users
---
## Quick Reference
**New to the project?** Start here for quick navigation:
| Understand the overall design | [The 4-Layer Design Philosophy](#the-4-layer-design-philosophy) |
| Add a new mathematical function (sin, log, etc.) | [Adding Mathematical Functions](#adding-mathematical-functions) |
| Add support for a new numeric backend | [Adding a New Backend](#adding-a-new-backend) |
| Understand error handling patterns | [Error Handling Architecture](#error-handling-architecture) |
| Optimize performance | [Performance Considerations](#performance-considerations) |
| Work with the macro system | [Macro System](#macro-system) |
| See common design patterns | [Design Patterns Reference](#design-patterns-reference) |
**Quick Examples:**
- **User documentation** → [README.md](../README.md), [COOKBOOK.md](COOKBOOK.md)
- **Migration guide** → [MIGRATION.md](MIGRATION.md)
- **Quality analysis** → [TECHNICAL_REVIEW.md](TECHNICAL_REVIEW.md)
---
## Table of Contents
1. [Overview](#overview)
2. [The 4-Layer Design Philosophy](#the-4-layer-design-philosophy)
3. [Layer 1: Raw Trait Contracts](#layer-1-raw-trait-contracts)
4. [Layer 2: Validation Policies](#layer-2-validation-policies)
5. [Layer 3: Validated Wrappers](#layer-3-validated-wrappers)
6. [Layer 4: High-Level Traits](#layer-4-high-level-traits)
7. [Adding a New Backend](#adding-a-new-backend)
8. [Adding Mathematical Functions](#adding-mathematical-functions)
9. [Error Handling Architecture](#error-handling-architecture)
10. [Macro System](#macro-system)
11. [Performance Considerations](#performance-considerations)
12. [Design Patterns Reference](#design-patterns-reference)
---
## Overview
`num-valid` implements a **layered architecture** that separates computational logic from validation logic, enabling zero-overhead abstractions while maintaining type-level safety guarantees.
### Core Design Principles
1. **Separation of Concerns**: Raw operations (Layer 1) are independent of validation (Layer 2)
2. **Zero-Cost Abstractions**: Validation overhead can be eliminated in release builds via policies
3. **Type-Level Guarantees**: Invalid states are unrepresentable (e.g., NaN in validated types)
4. **Backend Agnostic**: Support for both native f64 and arbitrary-precision arithmetic
5. **Composability**: Layers are orthogonal and can be mixed/matched
### Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Layer 4: High-Level Traits (FpScalar, RealScalar, etc.) │
│ - User-facing generic programming interface │
│ - Trait bounds for algorithms │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: Validated Wrappers (RealValidated<K>) │
│ - Newtype pattern with validation │
│ - Implements high-level traits │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: Validation Policies (NumKernel) │
│ - StrictFinitePolicy, DebugValidationPolicy │
│ - GuaranteesFiniteRealValues marker trait │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: Raw Trait Contracts (RawRealTrait, etc.) │
│ - Unchecked operations (unchecked_sqrt, unchecked_ln) │
│ - Implemented by f64, Complex<f64>, rug::Float │
└─────────────────────────────────────────────────────────────┘
```
---
## The 4-Layer Design Philosophy
### Why Four Layers?
Each layer has a single, well-defined responsibility:
| **1** | Pure computation without validation | `f64::unchecked_sqrt(x)` |
| **2** | Validation policy configuration | `StrictFinitePolicy` rejects NaN/Inf |
| **3** | Validated computation | `RealValidated::sqrt()` validates input & output |
| **4** | Generic interface | `fn compute<T: RealScalar>(x: T)` |
This separation enables:
- **Testing**: Each layer can be tested independently
- **Flexibility**: Change validation policy without touching computation
- **Performance**: Compile-time elimination of validation in release builds
- **Extensibility**: Add new backends by implementing Layer 1 only
---
## Layer 1: Raw Trait Contracts
**Location**: `src/core/traits/raw.rs`
### Core Traits
```rust
pub trait RawScalarTrait: Sized + Clone {
type ValidationErrors: std::error::Error;
// Validation is caller's responsibility
fn unchecked_add(self, other: Self) -> Self;
fn unchecked_mul(self, other: Self) -> Self;
// ... more operations
}
pub trait RawRealTrait: RawScalarTrait {
fn unchecked_sqrt(self) -> Self;
fn unchecked_reciprocal(self) -> Self;
fn unchecked_ln(self) -> Self;
// ... more real-specific operations
}
pub trait RawComplexTrait: RawScalarTrait {
type RealType: RawRealTrait;
fn unchecked_from_polar(r: Self::RealType, theta: Self::RealType) -> Self;
// ... more complex-specific operations
}
```
### Contract: The `unchecked_*` Naming Convention
**CRITICAL**: All methods in Layer 1 are prefixed with `unchecked_*` to communicate:
1. **No input validation**: Caller guarantees input validity
2. **No output validation**: Result may be NaN/Inf if input was invalid
3. **Performance**: Zero overhead, direct hardware/library call
**Example Contract Documentation**:
```rust
/// Computes the square root without validation.
///
/// # Contract
///
/// The caller MUST guarantee that `self` is:
/// - Finite (not NaN or infinity)
/// - Non-negative (for real numbers)
///
/// If the contract is violated, the result is unspecified (may be NaN).
fn unchecked_sqrt(self) -> Self;
```
### When to Implement Raw Traits
Implement `RawRealTrait` when adding a new numeric backend:
- Native types: `f64`, `f32`
- Arbitrary precision: `rug::Float`, `bigdecimal::BigDecimal`
- Fixed-point: Custom fixed-point types
- Symbolic: Expression trees (advanced)
**Implementation Checklist**:
- [ ] All methods return correct mathematical results for valid inputs
- [ ] Invalid inputs produce predictable outputs (NaN for reals)
- [ ] Performance is optimal (no unnecessary checks)
- [ ] Tests verify correctness on valid inputs only
---
## Layer 2: Validation Policies
**Location**: `src/core/policies.rs`, `src/core/traits/validation.rs`
### The NumKernel Trait
The `NumKernel` trait bundles a raw type with its validation policy (defined in `src/core/traits.rs`):
```rust
pub trait NumKernel: RawKernel + Sized {
/// The validation policy for real numbers.
type RealPolicy: ValidationPolicyReal<
Value = Self::RawReal,
Error = <Self::RawReal as RawScalarTrait>::ValidationErrors,
>;
/// The validation policy for complex numbers.
type ComplexPolicy: ValidationPolicyComplex<
Value = Self::RawComplex,
Error = <Self::RawComplex as RawScalarTrait>::ValidationErrors,
>;
/// The final, high-level, validated real scalar type for this kernel.
type Real: RealScalar<RawReal = Self::RawReal>;
/// The final, high-level, validated complex scalar type for this kernel.
type Complex: ComplexScalar<RealType = Self::Real>;
}
```
### Validation Policies
#### StrictFinitePolicy
**Guarantees**: All values are finite and normal (not subnormal)
```rust
pub struct StrictFinitePolicy<ScalarType, const PRECISION: u32>;
impl<T: RawRealTrait, const P: u32> ValidationPolicyReal
for StrictFinitePolicy<T, P>
{
const PRECISION: u32 = P;
fn validate(value: &T) -> Result<(), ErrorsValidationRawReal<T>> {
if !value.is_finite() {
return Err(ErrorsValidationRawReal::NotFinite { /* ... */ });
}
if value.is_subnormal() && value != &T::zero() {
return Err(ErrorsValidationRawReal::Subnormal { /* ... */ });
}
Ok(())
}
}
```
**Use Cases**:
- Production code requiring strict correctness
- Numerical algorithms where NaN propagation must be prevented
- HashMap keys (requires `Eq` + `Hash`)
#### DebugValidationPolicy
**Guarantees**: Validation only in debug builds, zero overhead in release
```rust
pub struct DebugValidationPolicy<P: ValidationPolicy>(PhantomData<P>);
impl<P: ValidationPolicyReal> ValidationPolicyReal
for DebugValidationPolicy<P>
{
fn validate(value: &P::Value) -> Result<(), P::Error> {
#[cfg(debug_assertions)]
{
P::validate(value)
}
#[cfg(not(debug_assertions))]
{
Ok(())
}
}
}
```
**Use Cases**:
- Performance-critical hot paths
- Code where correctness is guaranteed by construction
- After thorough testing with `StrictFinitePolicy`
### The GuaranteesFiniteRealValues Marker Trait
This is a **key architectural pattern** enabling conditional trait implementations:
```rust
pub trait GuaranteesFiniteRealValues {}
impl<T, const P: u32> GuaranteesFiniteRealValues
for StrictFinitePolicy<T, P> {}
// NOT implemented for DebugValidationPolicy (release builds don't validate)
```
**Enables**:
```rust
// Only validated types with finite guarantees can be HashMap keys
impl<K> Eq for RealValidated<K>
where K::RealPolicy: GuaranteesFiniteRealValues {}
impl<K> Hash for RealValidated<K>
where K::RealPolicy: GuaranteesFiniteRealValues {}
```
### Creating Custom Validation Policies
**Example**: Permissive policy allowing infinities
```rust
pub struct AllowInfinityPolicy<T, const P: u32>(PhantomData<T>);
impl<T: RawRealTrait, const P: u32> ValidationPolicyReal
for AllowInfinityPolicy<T, P>
{
const PRECISION: u32 = P;
fn validate(value: &T) -> Result<(), ErrorsValidationRawReal<T>> {
if value.is_nan() {
return Err(ErrorsValidationRawReal::IsNaN { /* ... */ });
}
Ok(()) // Allow ±infinity
}
}
// Define kernel
pub struct AllowInfinityKernel;
impl NumKernel for AllowInfinityKernel {
type RawReal = f64;
type RawComplex = Complex<f64>;
type RealPolicy = AllowInfinityPolicy<f64, 53>;
type ComplexPolicy = AllowInfinityPolicy<Complex<f64>, 53>;
const PRECISION: u32 = 53;
}
// Use with validated wrapper
type RealAllowInf = RealValidated<AllowInfinityKernel>;
```
---
## Layer 3: Validated Wrappers
**Location**: `src/core/types.rs`
### The Newtype Pattern
```rust
#[repr(transparent)]
pub struct RealValidated<K: NumKernel> {
pub(crate) value: K::RawReal,
pub(crate) _phantom: PhantomData<K>,
}
```
**Key Properties**:
- `#[repr(transparent)]`: Same memory layout as underlying type
- `pub(crate) value`: Internal access for crate, opaque to users
- `PhantomData<K>`: Zero-sized marker for kernel type
### Conditional Copy Implementation
**Pattern**: Automatically derive `Copy` when raw type is `Copy`
```rust
impl<K> Copy for RealValidated<K>
where
K: NumKernel,
K::RawReal: Copy
{}
```
**Result**:
- `RealNative64StrictFinite` is `Copy` (wraps `f64`)
- `RealRugStrictFinite<100>` is `Clone` only (wraps `rug::Float`)
### Validation Pattern
**Standard flow**: Validate input → unchecked operation → validate output
```rust
impl<K: NumKernel> RealValidated<K> {
pub fn sqrt(self) -> Result<Self, SqrtRealErrors<K::RawReal>> {
// 1. Validate input
K::RealPolicy::validate(&self.value)
.map_err(|e| SqrtRealErrors::Input {
source: SqrtRealInputErrors::InvalidArgument { source: e }
})?;
// Domain check
if self.value < K::RawReal::zero() {
return Err(SqrtRealErrors::Input {
source: SqrtRealInputErrors::NegativeValue { value: self.value }
});
}
// 2. Unchecked operation (fast)
let result = self.value.unchecked_sqrt();
// 3. Validate output
K::RealPolicy::validate(&result)
.map_err(|e| SqrtRealErrors::Output { source: e })?;
Ok(RealValidated {
value: result,
_phantom: PhantomData
})
}
}
```
### Panicking vs Fallible Methods
**Pattern**: Provide both variants for user convenience
```rust
impl<K: NumKernel> RealValidated<K> {
/// Fallible version (returns Result)
pub fn try_sqrt(self) -> Result<Self, SqrtRealErrors<K::RawReal>> {
// ... implementation from above
}
/// Panicking version (unwraps or panics)
pub fn sqrt(self) -> Self {
self.try_sqrt().unwrap_or_else(|e| {
panic!("sqrt failed: {}", e)
})
}
}
```
**Naming Convention**:
- `try_*`: Returns `Result`, never panics
- `*` (no prefix): Panics on error (for convenience/literals)
**Documentation**: Always clarify that panicking methods are **memory-safe** (not `unsafe`):
```rust
/// Computes the square root and returns the value directly.
///
/// This is a **panicking method** (not `unsafe`) that panics on invalid input.
/// For error handling, use [`try_sqrt`](Self::try_sqrt).
///
/// # Panics
///
/// Panics if the input is negative or if validation fails.
```
---
## Layer 4: High-Level Traits
**Location**: `src/lib.rs`
### The Trait Hierarchy
```rust
pub trait FpScalar: ScalarCore {
type Kind: scalar_kind::Sealed; // Real or Complex
type RealType: RealScalar; // Self for reals, component type for complex
// Common operations for all scalars
fn abs(self) -> Self::RealType;
fn kernel_signum(self) -> Self;
// ...
}
pub trait RealScalar: FpScalar<Kind = scalar_kind::Real> {
// Real-specific operations
fn sqrt(self) -> Self;
fn ln(self) -> Self;
// ...
}
pub trait ComplexScalar: FpScalar<Kind = scalar_kind::Complex> {
// Complex-specific operations
fn into_polar(self) -> (Self::RealType, Self::RealType);
// ...
}
```
### Sealed Trait Pattern for Mutual Exclusion
**Problem**: A type should be **either** real **or** complex, never both.
**Solution**: Sealed trait with two implementors
```rust
mod scalar_kind {
pub trait Sealed {}
pub struct Real;
pub struct Complex;
impl Sealed for Real {}
impl Sealed for Complex {}
}
pub trait FpScalar {
type Kind: scalar_kind::Sealed; // Can only be Real or Complex
}
```
**Result**: Type system enforces mutual exclusion at compile time.
### Writing Generic Algorithms
**Example**: Euclidean norm
```rust
pub fn euclidean_norm<T: RealScalar>(values: &[T]) -> T {
values.iter()
.map(|x| x.clone() * x.clone())
.sum::<T>()
.sqrt()
}
// Works for any RealScalar:
let norm_f64 = euclidean_norm(&[3.0, 4.0]); // f64
let norm_validated = euclidean_norm(&[
RealNative64StrictFinite::from_f64(3.0),
RealNative64StrictFinite::from_f64(4.0),
]);
```
### The ScalarCore Trait Alias
**Purpose**: Reduce repetitive bounds
```rust
pub trait ScalarCore = Sized + Clone + Debug + Display + PartialEq
+ Send + Sync + Zero + One + Serialize + DeserializeOwned + 'static;
// Instead of:
pub trait FpScalar: Sized + Clone + Debug + Display + PartialEq
+ Send + Sync + Zero + One + Serialize + DeserializeOwned + 'static { }
// Write:
pub trait FpScalar: ScalarCore { }
```
**Benefits**:
- Single source of truth
- Easier maintenance
- Clearer intent
---
## Adding a New Backend
### Step-by-Step Guide
#### 1. Implement RawScalarTrait
```rust
use my_bignum::BigNum;
impl RawScalarTrait for BigNum {
type ValidationErrors = BigNumValidationError;
fn unchecked_add(self, other: Self) -> Self {
self + other // Direct delegation to library
}
fn unchecked_mul(self, other: Self) -> Self {
self * other
}
// ... implement all required methods
}
```
#### 2. Implement RawRealTrait or RawComplexTrait
```rust
impl RawRealTrait for BigNum {
fn unchecked_sqrt(self) -> Self {
self.sqrt() // Library's sqrt method
}
fn unchecked_ln(self) -> Self {
self.ln()
}
// ... implement all required methods
}
```
#### 3. Create NumKernel Configuration
```rust
pub struct BigNumKernel<const PRECISION: u32>;
impl<const P: u32> NumKernel for BigNumKernel<P> {
type RawReal = BigNum;
type RawComplex = Complex<BigNum>; // Or custom complex type
type RealPolicy = StrictFinitePolicy<BigNum, P>;
type ComplexPolicy = StrictFinitePolicy<Complex<BigNum>, P>;
const PRECISION: u32 = P;
}
```
#### 4. Define Type Aliases
```rust
pub type RealBigNumStrictFinite<const P: u32> = RealValidated<BigNumKernel<P>>;
pub type ComplexBigNumStrictFinite<const P: u32> = ComplexValidated<BigNumKernel<P>>;
```
#### 5. Test Matrix
Create comprehensive tests:
```rust
#[cfg(test)]
mod tests {
use super::*;
type Real = RealBigNumStrictFinite<100>;
#[test]
fn test_basic_arithmetic() {
let a = Real::from_f64(2.0);
let b = Real::from_f64(3.0);
assert_eq!(a + b, Real::from_f64(5.0));
}
#[test]
fn test_sqrt() {
let x = Real::from_f64(4.0);
assert_eq!(x.sqrt(), Real::from_f64(2.0));
}
#[test]
#[should_panic]
fn test_sqrt_negative() {
let x = Real::from_f64(-1.0);
x.sqrt(); // Should panic
}
// ... more tests
}
```
### Backend Implementation Checklist
- [ ] `RawScalarTrait` implemented
- [ ] `RawRealTrait` or `RawComplexTrait` implemented
- [ ] `NumKernel` configuration created
- [ ] Type aliases defined
- [ ] Unit tests for all operations
- [ ] Edge case tests (NaN, infinity, subnormal)
- [ ] Performance benchmarks
- [ ] Documentation with examples
- [ ] Integration with existing traits (if needed)
**See Also:**
- [Layer 1: Raw Trait Contracts](#layer-1-raw-trait-contracts) - For understanding the raw trait requirements
- [Performance Considerations](#performance-considerations) - For optimization patterns
- [Design Patterns Reference](#design-patterns-reference) - For implementation patterns
---
## Adding Mathematical Functions
### Template for New Functions
#### 1. Define Trait in `src/functions/<category>.rs`
```rust
/// Trait for computing the hyperbolic arccosine of a number.
///
/// Provides both a fallible method ([`try_acosh`](ACosH::try_acosh)) that
/// returns a [`Result`], and a panicking method ([`acosh`](ACosH::acosh))
/// that returns the value directly or panics on invalid input.
///
/// # Domain
///
/// - **Real**: x ≥ 1
/// - **Complex**: All finite values
pub trait ACosH: Sized {
type Error: std::error::Error;
/// Computes the hyperbolic arccosine and returns a [`Result`].
fn try_acosh(self) -> Result<Self, Self::Error>;
/// Computes the hyperbolic arccosine or panics on invalid input.
///
/// # Panics
///
/// Panics if the input is invalid (e.g., x < 1 for real numbers).
fn acosh(self) -> Self;
}
```
#### 2. Define Error Types
```rust
/// Input errors for acosh computation on real numbers.
#[derive(Debug, Error)]
pub enum ACosHRealInputErrors<RawReal: RawRealTrait> {
/// The argument is invalid (NaN, infinity, or subnormal).
#[error("the argument is invalid")]
InvalidArgument {
#[source]
source: RawReal::ValidationErrors,
},
/// The value is less than 1 (outside domain).
#[error("acosh domain error: value {value} < 1")]
ValueLessThanOne {
value: RawReal,
#[cfg(feature = "backtrace")]
backtrace: Backtrace,
},
}
/// Full error type combining input and output errors.
pub type ACosHRealErrors<RawReal> = FunctionErrors<
ACosHRealInputErrors<RawReal>,
ErrorsValidationRawReal<RawReal>
>;
```
#### 3. Implement for Raw Types
```rust
impl ACosH for f64 {
type Error = ACosHRealErrors<Self>;
fn try_acosh(self) -> Result<Self, Self::Error> {
// Validate input
Native64RawRealStrictFinitePolicy::validate(&self)
.map_err(|e| FunctionErrors::Input {
source: ACosHRealInputErrors::InvalidArgument { source: e }
})?;
// Domain check
if self < 1.0 {
return Err(FunctionErrors::Input {
source: ACosHRealInputErrors::ValueLessThanOne {
value: self,
#[cfg(feature = "backtrace")]
backtrace: capture_backtrace(),
}
});
}
// Compute
let result = self.acosh(); // f64's inherent method
// Validate output
Native64RawRealStrictFinitePolicy::validate(&result)
.map_err(|e| FunctionErrors::Output { source: e })?;
Ok(result)
}
fn acosh(self) -> Self {
self.try_acosh().unwrap()
}
}
```
#### 4. Implement for Complex (if applicable)
```rust
impl ACosH for Complex<f64> {
type Error = ACosHComplexErrors<Self>;
fn try_acosh(self) -> Result<Self, Self::Error> {
// Complex acosh is defined for all finite values
Native64RawComplexStrictFinitePolicy::validate(&self)
.map_err(|e| FunctionErrors::Input {
source: ACosHComplexInputErrors::InvalidArgument { source: e }
})?;
let result = self.acosh(); // Complex<f64>'s method
Native64RawComplexStrictFinitePolicy::validate(&result)
.map_err(|e| FunctionErrors::Output { source: e })?;
Ok(result)
}
fn acosh(self) -> Self {
self.try_acosh().unwrap()
}
}
```
#### 5. Implement for Validated Wrappers (using duplicate macro)
```rust
#[duplicate_item(
kernel_type;
[Native64StrictFinite];
[Native64StrictFiniteInDebug];
// Add rug kernels if feature enabled
)]
impl ACosH for RealValidated<kernel_type> {
type Error = ACosHRealErrors<kernel_type::RawReal>;
fn try_acosh(self) -> Result<Self, Self::Error> {
let result = self.value.try_acosh()?;
Ok(RealValidated {
value: result,
_phantom: PhantomData
})
}
fn acosh(self) -> Self {
self.try_acosh().unwrap()
}
}
```
#### 6. Write Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_acosh_valid() {
let x = RealNative64StrictFinite::from_f64(2.0);
let result = x.try_acosh().unwrap();
assert!((result.to_f64() - 1.3169578969248166).abs() < 1e-10);
}
#[test]
fn test_acosh_domain_error() {
let x = RealNative64StrictFinite::from_f64(0.5);
assert!(x.try_acosh().is_err());
}
#[test]
#[should_panic(expected = "domain error")]
fn test_acosh_panic() {
let x = RealNative64StrictFinite::from_f64(0.5);
x.acosh(); // Should panic
}
}
```
### Function Implementation Checklist
- [ ] Trait defined with clear documentation
- [ ] Domain documented (real and complex if applicable)
- [ ] Input error type created
- [ ] Output error type aliased (using `FunctionErrors`)
- [ ] Implemented for `f64`
- [ ] Implemented for `Complex<f64>` (if applicable)
- [ ] Implemented for `rug::Float` (if feature enabled)
- [ ] Implemented for validated wrappers (using macro)
- [ ] Unit tests for valid inputs
- [ ] Unit tests for domain errors
- [ ] Unit tests for validation errors
- [ ] Doctests in trait documentation
- [ ] Mathematical definition documented
**See Also:**
- [Error Handling Architecture](#error-handling-architecture) - For error type design patterns
- [Macro System](#macro-system) - For reducing boilerplate in implementations
- [COOKBOOK.md](COOKBOOK.md) - For practical usage examples of custom functions
---
## Error Handling Architecture
### Two-Phase Error Model
**Key Insight**: Errors occur at two distinct phases:
1. **Input Phase**: Argument validation and domain checking
2. **Output Phase**: Result validation (e.g., overflow detection)
### The FunctionErrors Struct
```rust
#[derive(Debug, Error)]
pub enum FunctionErrors<InputError, OutputError> {
#[error("input error")]
Input {
#[source]
source: InputError,
},
#[error("output error")]
Output {
#[source]
source: OutputError,
},
}
```
**Usage Pattern**:
```rust
pub type SqrtRealErrors<RawReal> = FunctionErrors<
SqrtRealInputErrors<RawReal>,
ErrorsValidationRawReal<RawReal>
>;
```
### Input Error Design
**Template**:
```rust
#[derive(Debug, Error)]
pub enum FunctionNameInputErrors<RawType: RawTrait> {
#[error("the argument is invalid")]
InvalidArgument {
#[source]
source: RawType::ValidationErrors,
},
#[error("specific domain error: {details}")]
DomainError {
details: String,
value: RawType,
#[cfg(feature = "backtrace")]
backtrace: Backtrace,
},
}
```
### Backtrace Handling
**Pattern**: Conditional backtrace capture
```rust
use crate::core::policies::capture_backtrace;
return Err(SomeError {
value: x,
#[cfg(feature = "backtrace")]
backtrace: capture_backtrace(),
});
```
**Implementation** (in `src/core/errors.rs`):
```rust
/// Captures a backtrace if the `backtrace` feature is enabled.
///
/// When the `backtrace` feature is enabled, this function captures a full backtrace.
/// When disabled (default), it returns a disabled backtrace with zero overhead.
#[inline(always)]
pub fn capture_backtrace() -> Backtrace {
#[cfg(feature = "backtrace")]
{
Backtrace::force_capture()
}
#[cfg(not(feature = "backtrace"))]
{
Backtrace::disabled()
}
}
/// Returns `true` if the `backtrace` feature is enabled.
#[inline(always)]
pub const fn is_backtrace_enabled() -> bool {
#[cfg(feature = "backtrace")]
{ true }
#[cfg(not(feature = "backtrace"))]
{ false }
}
```
### Error Propagation Example
```rust
fn complex_computation<T: RealScalar>(x: T) -> Result<T, MyError> {
// Input validation automatically done by try_sqrt
let sqrt_x = x.try_sqrt()
.map_err(|e| MyError::SqrtFailed { source: e })?;
let ln_x = sqrt_x.try_ln()
.map_err(|e| MyError::LnFailed { source: e })?;
Ok(ln_x)
}
```
---
## Macro System
### The duplicate_item! Macro
**Purpose**: Reduce boilerplate for repetitive implementations
**Location**: Used throughout codebase via `duplicate` crate
### Common Pattern: Trait Implementation for Multiple Kernels
```rust
#[duplicate_item(
kernel_type;
[Native64StrictFinite];
[Native64StrictFiniteInDebug];
)]
impl SomeTrait for RealValidated<kernel_type> {
// Implementation identical for both kernels
fn some_method(&self) -> SomeType {
// ...
}
}
```
**Expands to**:
```rust
impl SomeTrait for RealValidated<Native64StrictFinite> {
fn some_method(&self) -> SomeType { /* ... */ }
}
impl SomeTrait for RealValidated<Native64StrictFiniteInDebug> {
fn some_method(&self) -> SomeType { /* ... */ }
}
```
### Pattern: Function Trait Definition
```rust
#[duplicate::duplicate_item(
T try_func func trait_doc;
[Sin] [try_sin] [sin] ["Computes the sine"];
[Cos] [try_cos] [cos] ["Computes the cosine"];
[Tan] [try_tan] [tan] ["Computes the tangent"];
)]
#[doc = trait_doc]
pub trait T: Sized {
type Error: std::error::Error;
fn try_func(self) -> Result<Self, Self::Error>;
fn func(self) -> Self;
}
```
### The Modular Macro System
**Location**: `src/macros.rs`
**Purpose**: Generate validated wrapper types with all trait implementations using composable macros.
The macro system is organized into layers:
#### Layer 1: Helper Macros (Internal, prefixed with `__`)
- `__impl_validated_arithmetic_op!` - Binary operations (4 variants)
- `__impl_validated_arithmetic_op_assign!` - Assignment operations (2 variants)
- `__impl_validated_arithmetic_op_and_op_assign!` - Both binary + assignment
#### Layer 2: Struct Definition
- `define_validated_struct_type!` - Just the struct definition with derives
#### Layer 3: Trait Implementation Macros
- `impl_validated_core_traits!` - IntoInner, Clone, PartialEq
- `impl_validated_constructors!` - TryNewValidated, TryNew, new_unchecked
- `impl_validated_numeric_traits!` - Zero, One, FpChecks
- `impl_validated_arithmetic!` - Add, Sub, Mul, Div (all variants)
- `impl_validated_special_ops!` - Neg, NegAssign, MulAddRef
- `impl_validated_sum!` - Sum trait with Neumaier algorithm
#### Current Usage (in `src/core/types.rs`)
```rust
// Step 1: Define the struct
crate::define_validated_struct_type!(
RealValidated,
RealPolicy,
RawReal,
"A validated real number that is guaranteed to conform to a specific NumKernel.",
"RealValidated({})"
);
// Step 2: Implement traits using composable macros
crate::impl_validated_core_traits!(RealValidated, RawReal);
crate::impl_validated_constructors!(RealValidated, RealPolicy, RawReal);
crate::impl_validated_numeric_traits!(RealValidated, RealPolicy, RawReal);
crate::impl_validated_arithmetic!(RealValidated, RealPolicy);
crate::impl_validated_special_ops!(RealValidated, RealPolicy);
crate::impl_validated_sum!(RealValidated, RealPolicy);
```
**Benefits of Modular Approach**:
- **Fine-grained control**: Opt-in/out of specific trait implementations
- **Better compile times**: Only regenerate affected macros on changes
- **Easier debugging**: Smaller macro expansions to inspect
- **Extensibility**: Add new trait macros without touching existing ones
---
## Performance Considerations
### Zero-Cost Abstractions
**Key mechanisms**:
1. **`#[inline(always)]`** on validated wrapper methods
2. **`#[repr(transparent)]`** for newtype wrappers
3. **Const generics** for compile-time precision
4. **Conditional compilation** for debug-only validation
### Validation Overhead
**StrictFinitePolicy in Release**:
- Overhead: ~5-15% compared to raw `f64`
- Benefit: NaN propagation prevented, type safety guaranteed
**DebugValidationPolicy in Release**:
- Overhead: **0%** (validation compiled out)
- Benefit: Full validation during development
### Optimization: MPFR Constants (rug backend)
**Pattern**: Use precomputed MPFR constants
```rust
// SLOW (recalculates π every time)
fn slow_pi(precision: u32) -> rug::Float {
rug::Float::with_val(precision, -1).acos() // ~50-100 µs
}
// FAST (uses MPFR constant)
fn fast_pi(precision: u32) -> rug::Float {
rug::Float::with_val(precision, rug::float::Constant::Pi) // ~5-10 µs
}
```
**Available Constants**:
- `Pi`
- `Log2`
- `Euler`
- `Catalan`
### Reference vs Value Semantics
**Guideline**: For rug types, prefer references to avoid clones
```rust
// GOOD: Reference-based
fn compute_with_refs(a: &RealRugStrictFinite<100>, b: &RealRugStrictFinite<100>) {
let result = a + b; // Only one allocation for result
}
// LESS EFFICIENT: Value-based
fn compute_with_values(a: RealRugStrictFinite<100>, b: RealRugStrictFinite<100>) {
let result = a + b; // Two moves, same allocation
}
```
**All arithmetic ops support 4 variants**:
- `T op T`
- `&T op T`
- `T op &T`
- `&T op &T`
---
## Design Patterns Reference
### 1. Newtype Pattern with Phantom Data
```rust
#[repr(transparent)]
pub struct Validated<K: Kernel> {
value: K::Raw,
_phantom: PhantomData<K>,
}
```
**Benefits**:
- Zero runtime cost
- Type-level configuration (K)
- Memory layout identical to raw type
### 2. Marker Trait for Conditional Implementation
```rust
pub trait GuaranteesFiniteRealValues {}
impl<K> Eq for Validated<K>
where K::Policy: GuaranteesFiniteRealValues {}
```
**Use Case**: Only policies that guarantee finite values can implement `Eq`/`Hash`.
### 3. Sealed Trait for Closed Set
```rust
mod sealed {
pub trait Sealed {}
pub struct TypeA;
pub struct TypeB;
impl Sealed for TypeA {}
impl Sealed for TypeB {}
}
pub trait PublicTrait {
type Kind: sealed::Sealed; // Can only be TypeA or TypeB
}
```
**Use Case**: `FpScalar::Kind` can only be `Real` or `Complex`.
### 4. Const Generic Precision
```rust
pub struct RealRug<const PRECISION: u32>;
// Compile-time error if mismatched
fn compute() {
let a: RealRug<100> = /* ... */;
let b: RealRug<200> = /* ... */;
let c = a + b; // ERROR: type mismatch
}
```
### 5. Trait Alias for Bounds Reduction
```rust
pub trait ScalarCore = Sized + Clone + Debug + /* ... */;
// Instead of repeating all bounds
pub trait FpScalar: ScalarCore { /* ... */ }
```
### 6. Panicking + Fallible Pair
```rust
pub trait Sqrt {
fn try_sqrt(self) -> Result<Self, Error>;
fn sqrt(self) -> Self { self.try_sqrt().unwrap() }
}
```
**Convention**: `try_*` returns `Result`, `*` panics (but is memory-safe).
### 7. Progressive Disclosure Documentation
```rust
/// Short summary (3-5 lines).
///
/// <details>
/// <summary>Detailed Behavior and Edge Cases</summary>
///
/// [100+ lines of comprehensive documentation]
///
/// </details>
```
**Use Case**: Complex methods like `truncate_to_usize`.
---
## Conclusion
This architecture guide provides a complete reference for understanding and contributing to `num-valid`. The 4-layer design is the core innovation that enables:
- **Type safety** without runtime overhead
- **Backend flexibility** (f64, rug, custom types)
- **Validation configurability** (strict, debug-only, custom)
- **Generic programming** via high-level traits
When in doubt:
1. Check existing implementations for patterns
2. Refer to this guide for architectural decisions
3. Ask maintainers for clarification
**Happy contributing!** 🦀
---
**Document Version**: 1.2
**Compatibility**: num-valid 0.3.2+
**Feedback**: Please open an issue for corrections or improvements