demo/
demo.rs

1// This is the same example also used in the README.md. When updating this, don't forget updating
2// the README.md as well. This is mainly used to test the code and generate the output shown.
3
4use core::{error::Error, fmt};
5
6use error_stack::{Report, ResultExt as _};
7
8#[derive(Debug)]
9struct ParseExperimentError;
10
11impl fmt::Display for ParseExperimentError {
12    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
13        fmt.write_str("invalid experiment description")
14    }
15}
16
17impl Error for ParseExperimentError {}
18
19#[expect(
20    clippy::manual_try_fold,
21    reason = "false-positive, try_fold is fail-fast, our implementation is fail-slow"
22)]
23fn parse_experiment(description: &str) -> Result<Vec<(u64, u64)>, Report<ParseExperimentError>> {
24    let values = description
25        .split(' ')
26        .map(|value| {
27            value
28                .parse::<u64>()
29                .attach_with(|| format!("{value:?} could not be parsed as experiment"))
30        })
31        .map(|value| value.map(|ok| (ok, 2 * ok)))
32        .fold(Ok(vec![]), |accum, value| match (accum, value) {
33            (Ok(mut accum), Ok(value)) => {
34                accum.push(value);
35
36                Ok(accum)
37            }
38            (Ok(_), Err(err)) => Err(err.expand()),
39            (Err(accum), Ok(_)) => Err(accum),
40            (Err(mut accum), Err(err)) => {
41                accum.push(err);
42
43                Err(accum)
44            }
45        })
46        .change_context(ParseExperimentError)?;
47
48    Ok(values)
49}
50
51#[derive(Debug)]
52struct ExperimentError;
53
54impl fmt::Display for ExperimentError {
55    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
56        fmt.write_str("experiment error: could not run experiment")
57    }
58}
59
60impl Error for ExperimentError {}
61
62#[expect(
63    clippy::manual_try_fold,
64    reason = "false-positive, try_fold is fail-fast, our implementation is fail-slow"
65)]
66fn start_experiments(
67    experiment_ids: &[usize],
68    experiment_descriptions: &[&str],
69) -> Result<Vec<u64>, Report<[ExperimentError]>> {
70    let experiments = experiment_ids
71        .iter()
72        .map(|exp_id| {
73            let description = experiment_descriptions.get(*exp_id).ok_or_else(|| {
74                Report::new(ExperimentError)
75                    .attach(format!("experiment {exp_id} has no valid description"))
76            })?;
77
78            let experiments = parse_experiment(description)
79                .attach(format!("experiment {exp_id} could not be parsed"))
80                .change_context(ExperimentError)?;
81
82            let experiments = experiments
83                .into_iter()
84                .map(|(lhs, rhs)| move || lhs * rhs)
85                .collect::<Vec<_>>();
86
87            Ok(experiments)
88        })
89        .fold(
90            Ok(vec![]),
91            |accum, value: Result<_, Report<ExperimentError>>| match (accum, value) {
92                (Ok(mut accum), Ok(value)) => {
93                    accum.extend(value);
94
95                    Ok(accum)
96                }
97                (Ok(_), Err(err)) => Err(err.expand()),
98                (Err(accum), Ok(_)) => Err(accum),
99                (Err(mut accum), Err(err)) => {
100                    accum.push(err);
101
102                    Err(accum)
103                }
104            },
105        )
106        .attach("unable to set up experiments")?;
107
108    Ok(experiments.iter().map(|experiment| experiment()).collect())
109}
110
111fn main() -> Result<(), Report<[ExperimentError]>> {
112    let experiment_ids = &[0, 2, 3];
113    let experiment_descriptions = &["10", "20", "3o 4a"];
114    start_experiments(experiment_ids, experiment_descriptions)?;
115
116    Ok(())
117}