[](https://crates.io/crates/batch_result)
[](https://docs.rs/batch_result)
# batch_result
Do not fail fast ,collect results and errors then decide,
`batch_result` is dynamic result batch handling, aggregates results from multiple **independent tasks** and
classifies the overall outcome heuristically (e.g. *Excellent*, *Partial*, *Poor*).
This crate intentionally **does not model workflows**:
- it does not enforce execution order
- it does not express dependencies between phases
- it does not require mandatory completion of any phase
`batch_result` is designed for tasks where tasks are **independent**, and where
the goal is **evaluation and reporting**, not control flow or orchestration.
# When to Use batch_result
* Test / validation pipelines
* Tasks with independent phases
* Partial success is meaningful
* Diagnostics matter more than strict pass/fail
* You want evaluation, not control flow
# When Not to Use batch_result
* Strict workflows or state machines
* Tasks with required execution order
* Must-complete or transactional pipelines
* Security-critical or atomic operations
## The Problem
```rust
// Traditional approach - fails at first error
let results: Result<Vec<Data>, Error> = items
.iter()
.map(process_item)
.collect()?; // Stops at first error!
```
# The Solution
```rust
//add batch_result and toml to your dependencies
//for this example
//BoxedDynError is defined inside batch_result module
//as Box<dyn Error + Send +Sync>
fn main()->Result<(),batch_result::BoxedDynError>{
// main syntax
//add mut keyword in order to add tasks
let mut outcome=batch_result::Outcome::new();
outcome
.task("config",health_check::check_config)
.task("database", health_check::check_database)
.task("cache", health_check::check_cache)
.run();//. if you didnt call .run(), the .classify() will produce OutcomeClass::Empty
//in other words,⚠️ Tasks are **lazy**
//Nothing executes until you call `.run()`
match outcome.classify() {
//review enum batch_result::OutcomeClass for better understanding
batch_result::OutcomeClass::Excellent =>{
println!("Service healthy, starting...");
Ok(())
},
_=>{
eprintln!("startup checks failed:");
for (taskname,err) in outcome.map_errors() {
eprintln!("task {taskname} failed - {err}")
}
Err("startup aborted ! ".into())
}
}
}
//helpers to simulate the tasks
mod health_check{
use std::{error::Error, fs};
type BoxedDynError=Box<dyn Error + Send + Sync>;
pub fn check_config()->Result< () ,BoxedDynError>{
let content=fs::read_to_string("config.toml")?;
toml::from_str::<toml::Value>(&content)?;
Ok(())
}
pub fn check_database()->Result< () ,BoxedDynError>{
//simulate db ping
if std::env::var("DB_URL").is_err(){
return Err("DB_URL not set".into());
}
Ok(())
}
pub fn check_cache()->Result< () ,BoxedDynError>{
fs::create_dir_all("/tmp/myapp-cache")?;
Ok(())
}
}
```
# Another Example with Concrete return types
```rust
//add batch_result to your dependencies
fn main(){
//concrete success type
//note that:
// Outcome is generic over T
// all tasks must return Result<T, BoxedDynError>
//Errors may differ and are erased into `BoxedDynError`.
let mut outcome=batch_result::Outcome::new();
outcome
.task("cpu_temp", health_check::read_cpu_temp)
.task("disk_free", health_check::read_disk_free)
.task("mem_free", health_check::read_mem_free)
.run();
println!("System health : {:?}",outcome.classify());
for (name,value) in outcome.map_success(){
println!("{name} : {value}");
}
}
//helpers to demonstrate only
mod health_check{
use std::error::Error;
use batch_result::BoxedDynError;
pub fn read_cpu_temp()->Result<f64,BoxedDynError>{
//Simulated sensor read
Ok(63.4)
}
pub fn read_disk_free()->Result<f64,BoxedDynError>{
//Simulated disk query
Ok(128.2)
}
pub fn read_mem_free()->Result<f64,BoxedDynError>{
Err("memory sensor unavailable".into())
}
}
```
## Installation
```toml
[dependencies]
batch_result = "0.1.0"
```
### Why not `Result<Vec<T>, E>`?
| `Result<Vec<T>, E>` | Stops at first error |
| `batch_result` | Collects all outcomes |