scuttle_core/
termination.rs

1//! # Functionality Related to Early Solver Termination
2//!
3//! The experimental `Try` trait is implemented for types here for ergonomics reasons. This is why
4//! Scuttle requires nightly Rust.
5
6use std::fmt;
7
8/// Early termination reasons for [`Solve::solve`]
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum Termination {
11    /// Terminated because of maximum number of Pareto points reached
12    PPLimit,
13    /// Terminated because of maximum number of solutions reached
14    SolsLimit,
15    /// Terminated because of maximum number of candidates reached
16    CandidatesLimit,
17    /// Terminated because of maximum number of oracle calls reached
18    OracleCallsLimit,
19    /// Termination because of external interrupt
20    Interrupted,
21}
22
23impl fmt::Display for Termination {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Termination::PPLimit => {
27                write!(f, "Solver terminated early because of Pareto point limit")
28            }
29            Termination::SolsLimit => {
30                write!(f, "Solver terminated early because of solution limit")
31            }
32            Termination::CandidatesLimit => {
33                write!(f, "Solver terminated early because of candidate limit")
34            }
35            Termination::OracleCallsLimit => {
36                write!(f, "Solver terminated early because of oracle call limit")
37            }
38            Termination::Interrupted => {
39                write!(f, "Solver terminated early because of interrupt signal")
40            }
41        }
42    }
43}
44
45/// Return type for functions that either return a value or were terminated early for some reason
46#[derive(Debug, PartialEq)]
47pub enum MaybeTerminated<T = ()> {
48    /// The operation finished with a return value
49    Done(T),
50    /// The operation was terminated early
51    Terminated(Termination),
52}
53
54impl<T> MaybeTerminated<T> {
55    pub fn unwrap(self) -> T {
56        match self {
57            MaybeTerminated::Done(val) => val,
58            MaybeTerminated::Terminated(term) => {
59                panic!("called `MaybeTerminated::unwrap()` on a `Terminated` value: {term}")
60            }
61        }
62    }
63}
64
65impl<T> std::ops::Try for MaybeTerminated<T> {
66    type Output = T;
67
68    type Residual = MaybeTerminated<std::convert::Infallible>;
69
70    fn from_output(output: Self::Output) -> Self {
71        MaybeTerminated::Done(output)
72    }
73
74    fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
75        match self {
76            MaybeTerminated::Done(val) => std::ops::ControlFlow::Continue(val),
77            MaybeTerminated::Terminated(term) => {
78                std::ops::ControlFlow::Break(MaybeTerminated::Terminated(term))
79            }
80        }
81    }
82}
83
84impl<T> std::ops::FromResidual<MaybeTerminated<std::convert::Infallible>> for MaybeTerminated<T> {
85    fn from_residual(residual: <Self as std::ops::Try>::Residual) -> Self {
86        let MaybeTerminated::Terminated(term) = residual;
87        MaybeTerminated::Terminated(term)
88    }
89}
90
91/// Return type for functions that either return a value, terminate early or error
92#[derive(Debug)]
93pub enum MaybeTerminatedError<T = ()> {
94    /// The operation finished with a return value
95    Done(T),
96    /// The operation was terminated early
97    Terminated(Termination),
98    /// The operation failed
99    Error(anyhow::Error),
100}
101
102impl<T> MaybeTerminatedError<T> {
103    pub fn unwrap(self) -> T {
104        match self {
105            MaybeTerminatedError::Done(val) => val,
106            MaybeTerminatedError::Terminated(term) => {
107                panic!("called `MaybeTerminatedError::unwrap()` on a `Terminated` value: {term}")
108            }
109            MaybeTerminatedError::Error(err) => {
110                panic!("called `MaybeTerminatedError::unwrap()` on an `Error` value: {err}")
111            }
112        }
113    }
114}
115
116impl<T> From<MaybeTerminated<T>> for MaybeTerminatedError<T> {
117    fn from(value: MaybeTerminated<T>) -> Self {
118        match value {
119            MaybeTerminated::Done(val) => MaybeTerminatedError::Done(val),
120            MaybeTerminated::Terminated(term) => MaybeTerminatedError::Terminated(term),
121        }
122    }
123}
124
125impl<T> From<anyhow::Result<T>> for MaybeTerminatedError<T> {
126    fn from(value: anyhow::Result<T>) -> Self {
127        match value {
128            Ok(val) => MaybeTerminatedError::Done(val),
129            Err(err) => MaybeTerminatedError::Error(err),
130        }
131    }
132}
133
134impl<T> std::ops::Try for MaybeTerminatedError<T> {
135    type Output = T;
136
137    type Residual = MaybeTerminatedError<std::convert::Infallible>;
138
139    fn from_output(output: Self::Output) -> Self {
140        MaybeTerminatedError::Done(output)
141    }
142
143    fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
144        match self {
145            MaybeTerminatedError::Done(val) => std::ops::ControlFlow::Continue(val),
146            MaybeTerminatedError::Terminated(term) => {
147                std::ops::ControlFlow::Break(MaybeTerminatedError::Terminated(term))
148            }
149            MaybeTerminatedError::Error(err) => {
150                std::ops::ControlFlow::Break(MaybeTerminatedError::Error(err))
151            }
152        }
153    }
154}
155
156impl<T> std::ops::FromResidual<MaybeTerminatedError<std::convert::Infallible>>
157    for MaybeTerminatedError<T>
158{
159    fn from_residual(residual: <Self as std::ops::Try>::Residual) -> Self {
160        match residual {
161            MaybeTerminatedError::Terminated(term) => MaybeTerminatedError::Terminated(term),
162            MaybeTerminatedError::Error(err) => MaybeTerminatedError::Error(err),
163        }
164    }
165}
166
167impl<T> std::ops::FromResidual<MaybeTerminated<std::convert::Infallible>>
168    for MaybeTerminatedError<T>
169{
170    fn from_residual(residual: MaybeTerminated<std::convert::Infallible>) -> Self {
171        let MaybeTerminated::Terminated(term) = residual;
172        MaybeTerminatedError::Terminated(term)
173    }
174}
175
176impl<T, E> std::ops::FromResidual<Result<std::convert::Infallible, E>> for MaybeTerminatedError<T>
177where
178    E: Into<anyhow::Error>,
179{
180    fn from_residual(residual: Result<std::convert::Infallible, E>) -> Self {
181        let Err(err) = residual;
182        MaybeTerminatedError::Error(err.into())
183    }
184}
185
186/// Equivalent of [`anyhow::ensure`] for [`MaybeTerminatedError`]
187macro_rules! ensure {
188    ($cond:expr, $msg:literal) => {
189        if !$cond {
190            return crate::MaybeTerminatedError::Error(anyhow::anyhow!($msg));
191        }
192    };
193    ($cond:expr, $err:expr) => {
194        if !$cond {
195            return crate::MaybeTerminatedError::Error(anyhow::anyhow!($err));
196        }
197    };
198    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
199        if !$cond {
200            return crate::MaybeTerminatedError::Error(anyhow::anyhow!($fmt, $($arg)*));
201        }
202    };
203}
204pub(crate) use ensure;