A Rust library for uncertainty-aware programming, implementing the approach from "Uncertain: A First-Order Type for Uncertain Data" by Bornholt, Mytkowicz, and McKinley.
Instead of treating uncertain data as exact values (which leads to bugs), this library uses evidence-based conditionals that account for uncertainty:
use uncertain_rs::Uncertain;
// Create uncertain values from probability distributions
let speed = Uncertain::normal(55.2, 5.0); // GPS reading with ±5 mph error
// Evidence-based conditional (returns Uncertain<bool>)
let speeding_evidence = speed.gt(60.0);
// Convert evidence to decision with confidence level
if speeding_evidence.probability_exceeds(0.95) {
// Only act if 95% confident
println!("Issue speeding ticket");
}- Evidence-based conditionals: Comparisons return evidence, not boolean facts
- Uncertainty propagation: Arithmetic operations preserve uncertainty
- Lazy evaluation: Computation graphs built lazily for efficiency
- Graph optimization: Common subexpression elimination and caching for performance
- SPRT hypothesis testing: Sequential Probability Ratio Test for optimal sampling
- Rich distributions: Normal, uniform, exponential, binomial, categorical, etc.
- Statistical analysis: Mean, std dev, confidence intervals, CDF, etc.
- Parallel sampling (optional): Multi-threaded sample generation using rayon
Add this to your Cargo.toml:
[dependencies]
uncertain-rs = "0.2.0"
# Enable parallel sampling (optional)
# uncertain-rs = { version = "0.2.0", features = ["parallel"] }use uncertain_rs::Uncertain;
fn main() {
// Create uncertain values
let x = Uncertain::normal(5.0, 1.0);
let y = Uncertain::normal(3.0, 0.5);
// Perform arithmetic operations
let sum = x.clone() + y.clone();
let product = x * y;
// Sample from the distributions
println!("Sum sample: {}", sum.sample());
println!("Product sample: {}", product.sample());
// Statistical analysis
println!("Sum mean: {}", sum.expected_value(1000));
println!("Sum std dev: {}", sum.standard_deviation(1000));
}For more examples, see the examples directory.
The library uses a custom UncertainError type for type-safe error handling:
use uncertain_rs::{Uncertain, UncertainError};
let result = Uncertain::<f64>::mixture(vec![], None);
match result {
Err(UncertainError::EmptyComponents) => {
println!("Error: No components provided");
}
Err(e) => println!("Error: {}", e),
Ok(dist) => { /* use dist */ }
}Enable the parallel feature to unlock multi-threaded sample generation for significant performance improvements with large sample counts:
use uncertain_rs::Uncertain;
let normal = Uncertain::normal(0.0, 1.0);
// Sequential sampling
let samples = normal.take_samples(100_000);
// Parallel sampling (requires 'parallel' feature)
let samples_par = normal.take_samples_par(100_000); // ~2-4x faster on multi-core systems
// Parallel + caching for f64 distributions
let gamma = Uncertain::gamma(2.0, 1.0);
let cached = gamma.take_samples_cached_par(100_000); // Fast generation + reuseWhen to use parallel sampling:
- Large sample counts (typically > 1,000)
- Expensive sampling operations (complex transformations, costly distributions)
- Multi-core systems available for parallelization
- Monte Carlo simulations and statistical analysis
See the parallel sampling example for benchmarks and use cases.
The library includes a computation graph optimizer that can eliminate common subexpressions and improve performance:
use uncertain_rs::{Uncertain, computation::GraphOptimizer};
// Create an expression with common subexpressions
let x = Uncertain::normal(2.0, 0.1);
let y = Uncertain::normal(3.0, 0.1);
let z = Uncertain::normal(1.0, 0.1);
// Expression: (x + y) * (x + y) + (x + y) * z
// The subexpression (x + y) appears 3 times
let sum = x.clone() + y.clone();
let expr = (sum.clone() * sum.clone()) + (sum * z);
// Apply optimization to eliminate common subexpressions
let mut optimizer = GraphOptimizer::new();
let optimized = optimizer.eliminate_common_subexpressions(expr.into_computation_node());
// The optimized graph reuses the (x + y) subexpression
println!("Cache size: {}", optimizer.subexpression_cache.len());We use just as a task runner. Available commands:
just fmt- Format codejust lint- Run clippy lintingjust test- Run testsjust audit- Security audit (check for vulnerabilities)just dev- Run the full development workflow (format + lint + test + audit)
This project takes security seriously. We run cargo audit to check for known vulnerabilities in dependencies:
- CI: Automated security audits run on every push and PR
- Local: Run
just auditorcargo auditbefore submitting changes - Installation: If you don't have
cargo-audit, runcargo install cargo-audit
The security audit checks all dependencies against the RustSec Advisory Database.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.