pub trait MisconfigurationStrategy {
type Error<E>;
// Required methods
fn fallback<T, E>(
&self,
result: Result<T, E>,
fallback_fn: impl FnOnce(E) -> T,
) -> Result<T, Self::Error<E>>;
fn fallback_opt<T, E>(
&self,
result: Result<T, E>,
fallback_fn: impl FnOnce(E),
) -> Result<Option<T>, Self::Error<E>>;
fn to_anyhow<T, E>(
&self,
result: Result<T, Self::Error<E>>,
) -> Result<T, Self::Error<Error>>
where Error: From<E>;
fn map_err<T, E1, E2>(
&self,
result: Result<T, Self::Error<E1>>,
map_err: impl FnOnce(E1) -> E2,
) -> Result<T, Self::Error<E2>>;
}Expand description
Generic handling of two possible approaches to an Error:
FallibleStrategy: The code should simply failUseDefaultStrategy: The code should apply default values and never fail
Any function that wants to be made generic over these approaches should be changed thusly.
Old:
fn do_thing()
-> Result<T, E>
{
let x = something_fallible()?;
Ok(x)
}New:
fn do_thing<Strategy: MisconfigurationStrategy>(strategy: &Strategy)
-> Result<T, Strategy::Error<E>>
{
let x = strategy.fallback(something_fallible(), |err| {
tracing::debug!("Failed to get value: {err}");
MyType::default()
})?;
Ok(x)
}The key trick is instead of returning Result<T, E> your function should
return Result<T, Strategy::Error<E>>. Which simplifies to:
FallibleStrategy:Result<T, E>UseDefaultStrategy:Result<T, Infallible>~=T
Notably, if your function returns Result<T, Strategy::Error<E>> you will
be statically prevented from returning an Err without going through
MisconfigurationStrategy::fallback or MisconfigurationStrategy::fallback_opt
which ensure you’re handling both approaches (or you wrote an unwrap but
those standout far more than adding a new ? to a function that must be able to Not Fail).
Also, for any caller that passes in UseDefaultStrategy, they will be able
to write let Ok(val) = do_thing(&UseDefaultStrategy); instead of having to
write an unwrap().
Required Associated Types§
Required Methods§
Sourcefn fallback<T, E>(
&self,
result: Result<T, E>,
fallback_fn: impl FnOnce(E) -> T,
) -> Result<T, Self::Error<E>>
fn fallback<T, E>( &self, result: Result<T, E>, fallback_fn: impl FnOnce(E) -> T, ) -> Result<T, Self::Error<E>>
Try to get the value out of a Result that we need to proceed.
If UseDefaultStrategy, on Err this will call fallback_fn to compute
a default value and always return Ok.
If FallibleStrategy this is a no-op and will return the Result.
Sourcefn fallback_opt<T, E>(
&self,
result: Result<T, E>,
fallback_fn: impl FnOnce(E),
) -> Result<Option<T>, Self::Error<E>>
fn fallback_opt<T, E>( &self, result: Result<T, E>, fallback_fn: impl FnOnce(E), ) -> Result<Option<T>, Self::Error<E>>
Try to get the value out of a Result that we can do without.
If UseDefaultStrategy, this will call fallback_fn to report an issue
(i.e. you can invoke tracing::debug! or something) and then return None.
If FallibleStrategy this is a no-op and will return the Result (but Ok => Ok(Some)).
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".