Skip to main content

rust_elm/compose/
safe_reducer.rs

1//! Safe reducer updates — catch panics during `update` without crashing the runtime.
2use std::any::Any;
3use std::panic::{catch_unwind, AssertUnwindSafe};
4
5use crate::cmd::Cmd;
6
7/// A reducer panic caught by [`safe_reduce_update`] or [`CatchReducer`](crate::reducer::CatchReducer).
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct SafeReduceError {
10    message: Option<String>,
11}
12
13impl SafeReduceError {
14    pub fn message(&self) -> Option<&str> {
15        self.message.as_deref()
16    }
17
18    fn from_payload(payload: Box<dyn Any + Send>) -> Self {
19        let message = payload
20            .downcast_ref::<&str>()
21            .map(|s| (*s).to_string())
22            .or_else(|| payload.downcast_ref::<String>().cloned());
23        Self { message }
24    }
25}
26
27/// Run `reduce` inside [`catch_unwind`]. Default for [`CatchReducer`] and [`Runtime`](crate::Runtime) (with `runtime` feature):
28/// panics are caught but `state` is not reverted. Use [`safe_reduce_rollback`] for rollback.
29pub fn safe_reduce_update<S, M, F>(state: &mut S, reduce: F, action: M) -> Result<Cmd<M>, SafeReduceError>
30where
31    F: FnOnce(&mut S, M) -> Cmd<M>,
32{
33    match catch_unwind(AssertUnwindSafe(|| reduce(state, action))) {
34        Ok(cmd) => Ok(cmd),
35        Err(payload) => Err(SafeReduceError::from_payload(payload)),
36    }
37}
38
39/// Run `reduce` inside [`catch_unwind`], rolling back via `checkpoint` on panic.
40pub fn safe_reduce_rollback<S, M, F>(
41    state: &mut S,
42    checkpoint: &mut S,
43    reduce: F,
44    action: M,
45) -> Result<Cmd<M>, SafeReduceError>
46where
47    S: Clone,
48    F: FnOnce(&mut S, M) -> Cmd<M>,
49{
50    match catch_unwind(AssertUnwindSafe(|| reduce(state, action))) {
51        Ok(cmd) => {
52            checkpoint.clone_from(state);
53            Ok(cmd)
54        }
55        Err(payload) => {
56            std::mem::swap(state, checkpoint);
57            Err(SafeReduceError::from_payload(payload))
58        }
59    }
60}