# Migration Guide: From Raw Primitives to num-valid
**A practical guide for transitioning from `f64` and `Complex<f64>` to validated types**
---
## Table of Contents
1. [Why Migrate?](#why-migrate)
2. [Quick Start: Side-by-Side Comparison](#quick-start-side-by-side-comparison)
3. [Migration Strategy](#migration-strategy)
4. [Pattern-by-Pattern Migration](#pattern-by-pattern-migration)
5. [Common Pitfalls and Solutions](#common-pitfalls-and-solutions)
6. [Performance Considerations](#performance-considerations)
7. [Checklist](#checklist)
---
## Why Migrate?
### The Problem with Raw Primitives
```rust
// Raw f64 code - Silent failures
let x = user_input.parse::<f64>().unwrap(); // Could be NaN
let y = x.sqrt(); // NaN if x < 0
let z = y / 0.0; // Infinity
let result = z * 2.0; // Still Infinity
// Result: Invalid data propagates silently through your entire program
println!("Result: {}", result); // "inf"
```
### The Solution with num-valid
```rust
use num_valid::{RealNative64StrictFinite, RealScalar};
// Validated code - Explicit error handling
let x = user_input.parse::<f64>().unwrap();
let validated_x = RealNative64StrictFinite::try_from_f64(x)?; // Fails on NaN/Inf
let y = validated_x.try_sqrt()?; // Fails on negative
let z_inverse = y / RealNative64StrictFinite::zero(); // Fails (zero division)
// Result: Errors are caught immediately at the source
// Your program never enters an invalid state
```
**Key Benefits:**
- ✅ **Type Safety**: Invalid states are unrepresentable
- ✅ **Early Detection**: Errors caught at the point of creation
- ✅ **Clear Semantics**: `try_*` methods make failure cases explicit
- ✅ **Zero Cost**: Debug validation can be disabled in release builds
---
## Quick Start: Side-by-Side Comparison
### Basic Operations
#### Before (raw f64)
```rust
let x = 3.14_f64;
let y = 2.71_f64;
let sum = x + y;
let product = x * y;
let sqrt_x = x.sqrt();
```
#### After (num-valid - Recommended)
```rust
use num_valid::real;
let x = real!(3.14);
let y = real!(2.71);
let sum = x.clone() + &y;
let product = x.clone() * &y;
let sqrt_x = x.sqrt();
```
#### After (num-valid - Explicit)
```rust
use num_valid::{RealNative64StrictFinite, RealScalar};
let x = RealNative64StrictFinite::from_f64(3.14);
let y = RealNative64StrictFinite::from_f64(2.71);
let sum = x.clone() + &y;
let product = x.clone() * &y;
let sqrt_x = x.sqrt();
```
### Complex Numbers
#### Before (num::Complex)
```rust
use num::Complex;
let z1 = Complex::new(1.0, 2.0);
let z2 = Complex::new(3.0, 4.0);
let product = z1 * z2;
let magnitude = z1.norm(); // Can be NaN/Inf
```
#### After (num-valid)
```rust
use num_valid::{complex, functions::Abs};
let z1 = complex!(1.0, 2.0);
let z2 = complex!(3.0, 4.0);
let product = z1.clone() * &z2;
let magnitude = z1.abs(); // Validated, never NaN/Inf
```
---
## Migration Strategy
### Phase 1: Identify Boundary Points
**Goal**: Find where external data enters your system.
```rust
// Example: Data entry points to validate
fn load_from_file(path: &str) -> Vec<f64> { /* ... */ }
fn parse_user_input(s: &str) -> f64 { /* ... */ }
fn receive_network_data() -> Vec<f64> { /* ... */ }
// Migration: Validate at boundaries
use num_valid::{RealNative64StrictFinite, try_vec_f64_into_vec_real};
fn load_from_file_validated(path: &str) -> Result<Vec<RealNative64StrictFinite>, Error> {
let raw_data = load_from_file(path);
try_vec_f64_into_vec_real(raw_data)
.map_err(|e| Error::InvalidData(e.to_string()))
}
```
### Phase 2: Convert Core Algorithms
**Goal**: Make internal algorithms generic over scalar types.
```rust
// Before: Tied to f64
fn euclidean_distance(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f64>()
.sqrt()
}
// After: Generic over any RealScalar
use num_valid::{RealScalar, functions::Abs};
fn euclidean_distance<T: RealScalar>(a: &[T], b: &[T]) -> T {
a.iter().zip(b.iter())
.map(|(x, y)| {
let diff = x.clone() - y.clone();
diff.clone() * diff
})
.sum::<T>()
.sqrt()
}
// Now works with f64, RealNative64StrictFinite, RealRugStrictFinite, etc.
```
### Phase 3: Gradual Internal Migration
**Goal**: Convert internal code incrementally.
```rust
// Start with type aliases for gradual migration
type Real = RealNative64StrictFinite;
type Complex = ComplexNative64StrictFinite;
// Old code gradually becomes:
fn old_function(x: Real, y: Real) -> Real {
(x * y).sqrt()
}
// Eventually you can make it generic:
fn new_function<T: RealScalar>(x: T, y: T) -> T {
(x * y).sqrt()
}
```
---
## Pattern-by-Pattern Migration
### Pattern 1: Constants
#### Before
```rust
const PI: f64 = std::f64::consts::PI;
const E: f64 = std::f64::consts::E;
let area = PI * r * r;
```
#### After (Lazy Evaluation)
```rust
use num_valid::{RealNative64StrictFinite, Constants};
// Use lazy static for runtime constants
use std::sync::LazyLock;
);
let area = &*PI * r.clone() * r;
```
#### After (Inline)
```rust
use num_valid::real;
let area = real!(std::f64::consts::PI) * r.clone() * r;
```
### Pattern 2: Function Arguments
#### Before
```rust
fn compute(x: f64, y: f64) -> f64 {
x.sqrt() + y.sqrt()
}
```
#### After (Generic)
```rust
use num_valid::RealScalar;
fn compute<T: RealScalar>(x: T, y: T) -> T {
x.sqrt() + y.sqrt()
}
```
#### After (Concrete Type)
```rust
use num_valid::RealNative64StrictFinite;
fn compute(x: RealNative64StrictFinite, y: RealNative64StrictFinite) -> RealNative64StrictFinite {
x.sqrt() + y.sqrt()
}
```
### Pattern 3: Structs and Fields
#### Before
```rust
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self, other: &Point) -> f64 {
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
}
}
```
#### After
```rust
use num_valid::RealNative64StrictFinite;
struct Point {
x: RealNative64StrictFinite,
y: RealNative64StrictFinite,
}
impl Point {
fn distance(&self, other: &Point) -> RealNative64StrictFinite {
let dx = &self.x - &other.x;
let dy = &self.y - &other.y;
(dx.clone() * dx + dy.clone() * dy).sqrt()
}
}
```
#### After (Generic)
```rust
use num_valid::RealScalar;
struct Point<T: RealScalar> {
x: T,
y: T,
}
impl<T: RealScalar> Point<T> {
fn distance(&self, other: &Point<T>) -> T {
let dx = self.x.clone() - &other.x;
let dy = self.y.clone() - &other.y;
(dx.clone() * dx + dy.clone() * dy).sqrt()
}
}
```
### Pattern 4: Error Handling
#### Before (Unchecked)
```rust
fn divide(a: f64, b: f64) -> f64 {
a / b // Can be Inf or NaN
}
```
#### After (Explicit Errors)
```rust
use num_valid::{RealNative64StrictFinite, RealScalar};
use thiserror::Error;
#[derive(Debug, Error)]
enum MathError {
#[error("Division by zero")]
DivisionByZero,
#[error("Invalid input: {0}")]
InvalidInput(String),
}
fn divide(a: f64, b: f64) -> Result<RealNative64StrictFinite, MathError> {
let validated_a = RealNative64StrictFinite::try_from_f64(a)
.map_err(|e| MathError::InvalidInput(e.to_string()))?;
let validated_b = RealNative64StrictFinite::try_from_f64(b)
.map_err(|e| MathError::InvalidInput(e.to_string()))?;
if validated_b == RealNative64StrictFinite::zero() {
return Err(MathError::DivisionByZero);
}
Ok(validated_a / validated_b)
}
```
### Pattern 5: Iterators and Collections
#### Before
```rust
fn sum_squares(values: &[f64]) -> f64 {
values.iter().map(|x| x * x).sum()
}
```
#### After (Generic)
```rust
use num_valid::RealScalar;
fn sum_squares<T: RealScalar>(values: &[T]) -> T {
values.iter()
.map(|x| x.clone() * x.clone())
.sum()
}
```
#### After (With Conversion)
```rust
use num_valid::{RealNative64StrictFinite, try_vec_f64_into_vec_real};
fn sum_squares_validated(values: Vec<f64>) -> Result<RealNative64StrictFinite, String> {
let validated = try_vec_f64_into_vec_real(values)
.map_err(|e| e.to_string())?;
Ok(validated.iter()
.map(|x| x.clone() * x.clone())
.sum())
}
```
---
## Common Pitfalls and Solutions
### Pitfall 1: Forgetting `.clone()` or `&`
#### Problem
```rust
use num_valid::real;
let x = real!(3.0);
let y = x + x; // ❌ Error: value moved
```
#### Solution
```rust
let x = real!(3.0);
let y = x.clone() + x.clone(); // ✅ Clone for value types
// OR
let y = &x + &x; // ✅ Use references (works for all combinations)
```
### Pitfall 2: Type Inference Issues
#### Problem
```rust
use num_valid::RealScalar;
let x = RealScalar::zero(); // ❌ Error: cannot infer type
```
#### Solution
```rust
use num_valid::RealNative64StrictFinite;
let x = RealNative64StrictFinite::zero(); // ✅ Explicit type
// OR
let x: RealNative64StrictFinite = num::Zero::zero(); // ✅ Type annotation
```
### Pitfall 3: Mixing f64 and Validated Types
#### Problem
```rust
use num_valid::real;
let x = real!(3.0);
let y = 2.0_f64;
let z = x + y; // ❌ Error: type mismatch
```
#### Solution
```rust
let x = real!(3.0);
let y = real!(2.0);
let z = x + y; // ✅ Same types
// OR
let x = real!(3.0);
let y = 2.0_f64;
let y_validated = RealNative64StrictFinite::from_f64(y);
let z = x + y_validated; // ✅ Convert first
```
### Pitfall 4: Performance Regression in Hot Loops
#### Problem
```rust
// Before: ~10 ns/iteration
for i in 0..1_000_000 {
let x = i as f64;
let y = x.sqrt();
}
// After: ~15 ns/iteration (50% slower!)
use num_valid::RealNative64StrictFinite;
for i in 0..1_000_000 {
let x = RealNative64StrictFinite::from_f64(i as f64);
let y = x.sqrt();
}
```
#### Solution
```rust
// Use debug-only validation for hot paths
use num_valid::RealNative64StrictFiniteInDebug;
for i in 0..1_000_000 {
let x = RealNative64StrictFiniteInDebug::from_f64(i as f64);
let y = x.sqrt();
}
// Performance: ~10 ns/iteration in release (same as raw f64)
```
---
## Performance Considerations
### Benchmarking Before and After
```rust
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use num_valid::RealNative64StrictFinite;
fn benchmark_raw_f64(c: &mut Criterion) {
c.bench_function("raw_f64_sqrt", |b| {
b.iter(|| {
let x = black_box(2.0_f64);
black_box(x.sqrt())
})
});
}
fn benchmark_validated(c: &mut Criterion) {
c.bench_function("validated_sqrt", |b| {
b.iter(|| {
let x = black_box(RealNative64StrictFinite::from_f64(2.0));
black_box(x.sqrt())
})
});
}
criterion_group!(benches, benchmark_raw_f64, benchmark_validated);
criterion_main!(benches);
```
**Expected Results:**
- Raw f64: ~2-3 ns
- Validated (StrictFinite): ~3-4 ns (10-30% overhead)
- Validated (StrictFiniteInDebug): ~2-3 ns in release (0% overhead)
### When to Use Each Type
| External input | `RealNative64StrictFinite` | Always validate untrusted data |
| Constants | `real!()` macro | Compile-time evaluation |
| Hot loops (internal) | `RealNative64StrictFiniteInDebug` | Zero overhead in release |
| API boundaries | `RealNative64StrictFinite` | Explicit validation |
| High precision | `RealRugStrictFinite<N>` | Arbitrary precision |
---
## Checklist
### Pre-Migration
- [ ] Identify all entry points for external data
- [ ] Profile current performance baselines
- [ ] Review error handling strategy
- [ ] Set up benchmarks for critical paths
### During Migration
- [ ] Add `num-valid` dependency to `Cargo.toml`
- [ ] Convert boundary functions first
- [ ] Make core algorithms generic
- [ ] Add comprehensive error handling
- [ ] Update tests to use validated types
- [ ] Run benchmarks to identify regressions
### Post-Migration
- [ ] Verify all tests pass
- [ ] Confirm performance is acceptable
- [ ] Update documentation
- [ ] Train team on new patterns
- [ ] Monitor production for issues
---
## Additional Resources
- **[Cookbook](COOKBOOK.md)**: Common patterns and examples
- **[Architecture Guide](ARCHITECTURE.md)**: Deep dive into design
- **[API Documentation](https://docs.rs/num-valid)**: Complete reference
- **[Technical Review](TECHNICAL_REVIEW.md)**: Expert analysis
---
## Need Help?
If you encounter issues during migration:
1. **Check the [Cookbook](COOKBOOK.md)** for similar patterns
2. **Review [API docs](https://docs.rs/num-valid)** for method details
3. **Open an issue** on [GitLab](https://gitlab.com/max.martinelli/num-valid)
4. **Start small**: Migrate one module at a time
---
**Document Version**: 1.0
**Last Updated**: December 3, 2025