pub trait ResultExt<T, E>: Sized {
// Required methods
fn widen<E2>(self) -> Result<T, E2>
where Self: WidenResult<T, E2>;
fn narrow_rejected(self) -> Result<T, <E as NarrowRejectedLane>::Narrowed>
where E: NarrowRejectedLane;
fn narrow_transient(
self,
attempts: u32,
) -> Result<T, <E as Laned>::WithoutTransient>
where E: Laned;
fn narrow_denied(self) -> Result<T, <E as NarrowDeniedLane>::Narrowed>
where E: NarrowDeniedLane;
fn rejected(
self,
) -> Result<Result<T, <E as IntoLanes>::Rejected>, Fault<<E as IntoLanes>::Lanes>>
where E: IntoLanes,
<E as IntoLanes>::Rejected: Rejection;
fn map_rejected<D2>(
self,
f: impl FnOnce(<E as IntoLanes>::Rejected) -> D2,
) -> Result<T, Fail<D2, <E as IntoLanes>::Lanes>>
where E: IntoLanes,
<E as IntoLanes>::Rejected: Rejection;
fn widen_via_builtin(
self,
) -> Result<T, <<E as IntoLanes>::Rejected as BuiltinFor<<E as IntoLanes>::Lanes>>::Builtin>
where E: IntoLanes,
<E as IntoLanes>::Rejected: BuiltinFor<<E as IntoLanes>::Lanes>;
fn classify<W>(self) -> Result<T, W>
where W: Classify + From<E>;
}Expand description
The one trait a consumer imports to move a Result between error
signatures: widen it to a bigger profile, narrow away a lane that is no
longer live at this boundary, hand the rejection to the caller as a value,
classify a foreign error into a local wrapper, or (under tracing) record
it onto the current span.
Required Methods§
Sourcefn widen<E2>(self) -> Result<T, E2>where
Self: WidenResult<T, E2>,
fn widen<E2>(self) -> Result<T, E2>where
Self: WidenResult<T, E2>,
Target-inferred widening for Fault or Fail results.
Fault results widen to Fault; Fail results widen to Fail,
converting the rejection through crate::Lift. Success values and
fault payloads are preserved. Widening can add lanes, but cannot
silently discard an enabled lane:
use errlanes::{Fault, ResultExt, lanes};
fn discard_denied(value: Result<(), Fault<lanes!(Denied, Fatal)>>)
-> Result<(), Fault<lanes!(Fatal)>>
{
value.widen()
}A Classify wrapper widens too — into a Fail as usual, or
straight into a Fault when it never rejects. That last form is how a
wrapper enters the lanes at a call site with no signature to infer the
destination from, above all on the way into a Box<dyn Error>, where
? on the bare wrapper would box it unlaned and lose its
classification (see classify.rs and the README’s box-boundary
section).
Sourcefn narrow_rejected(self) -> Result<T, <E as NarrowRejectedLane>::Narrowed>where
E: NarrowRejectedLane,
fn narrow_rejected(self) -> Result<T, <E as NarrowRejectedLane>::Narrowed>where
E: NarrowRejectedLane,
Narrows away the Rejected lane: a rejection with no caller left to
correct it becomes Fatal(Invariant), carrying the rejection as its
source. On a Fail<D, L> the result is Fault<L>. On a bare
Rejection — a public method that returns just R because its
caller can act on it, consumed by an internal frame that already
proved the precondition — the result is the bare Fatal, and ?
carries it into whatever Fault/Fail the function returns, so the
destination profile is never named at the call site:
use errlanes::{Fault, ResultExt, lanes};
#[derive(Debug, errlanes::Rejection)]
#[rejection(code = "LANE_DISABLED")]
struct LaneDisabled;
fn listen() -> Result<(), LaneDisabled> {
Err(LaneDisabled)
}
// The lane was required at registration: by now, off is an invariant.
fn run() -> Result<(), Fault<lanes!(Transient, Fatal)>> {
listen().narrow_rejected()?;
Ok(())
}
assert!(matches!(run(), Err(Fault::Fatal(_))));A Fault has no rejected lane to narrow, and that is a compile
error, not an identity:
use errlanes::{Fault, ResultExt, lanes};
fn narrow(value: Result<(), Fault<lanes!(Fatal)>>)
-> Result<(), Fault<lanes!(Fatal)>>
{
value.narrow_rejected()
}Sourcefn narrow_transient(
self,
attempts: u32,
) -> Result<T, <E as Laned>::WithoutTransient>where
E: Laned,
fn narrow_transient(
self,
attempts: u32,
) -> Result<T, <E as Laned>::WithoutTransient>where
E: Laned,
Narrows away the Transient lane: a caller-owned retry loop hands
back the narrowed profile once it stops retrying, so it stops
offering Transient to its own callers. attempts is what the loop
counted; an exhausted transient becomes Fatal(Exhausted) with the
last transient as its source.
Sourcefn narrow_denied(self) -> Result<T, <E as NarrowDeniedLane>::Narrowed>where
E: NarrowDeniedLane,
fn narrow_denied(self) -> Result<T, <E as NarrowDeniedLane>::Narrowed>where
E: NarrowDeniedLane,
Narrows away the Denied lane: a denial at a boundary with no
subject (code running as the system) becomes Fatal(Denied). Only
available where E’s profile enables Denied:
use errlanes::{Fault, ResultExt, lanes};
fn narrow(value: Result<(), Fault<lanes!(Transient, Fatal)>>)
-> Result<(), Fault<lanes!(Transient, Fatal)>>
{
value.narrow_denied()
}Sourcefn rejected(
self,
) -> Result<Result<T, <E as IntoLanes>::Rejected>, Fault<<E as IntoLanes>::Lanes>>where
E: IntoLanes,
<E as IntoLanes>::Rejected: Rejection,
fn rejected(
self,
) -> Result<Result<T, <E as IntoLanes>::Rejected>, Fault<<E as IntoLanes>::Lanes>>where
E: IntoLanes,
<E as IntoLanes>::Rejected: Rejection,
Hands the rejection to the caller as a value and keeps the faults
propagating: Result<T, Fail<D, L>> becomes
Result<Result<T, D>, Fault<L>>, the Result-level form of
Fail::rejected. The outer ? carries the faults on into any
enclosing carrier; the inner Result is the domain outcome, with the
rejection as its Err, matched right where it occurred:
use errlanes::{Fail, Fault, ResultExt, lanes};
#[derive(Debug, errlanes::Rejection)]
#[rejection(code = "TIMED_OUT")]
struct TimedOut;
fn await_completion() -> Result<u64, Fail<TimedOut, lanes!(Transient, Fatal)>> {
Err(Fail::Rejected(TimedOut))
}
fn poll_once() -> Result<Option<u64>, Fault<lanes!(Transient, Fatal)>> {
match await_completion().rejected()? {
Ok(outcome) => Ok(Some(outcome)),
Err(TimedOut) => Ok(None),
}
}
assert!(poll_once().unwrap().is_none());This is the dual of narrow_rejected: that
one is for a boundary with no caller left to correct the rejection,
this one for the call site that is going to. There is deliberately no
Option-returning accessor on a Result: an as_rejected() that
answered None for both Ok and a fault would be the one place a
lane could be dropped without naming it. Only available where E
carries a rejected lane; a Fault has none:
use errlanes::{Fault, ResultExt, lanes};
fn split(value: Result<(), Fault<lanes!(Fatal)>>) {
let _ = value.rejected();
}Sourcefn map_rejected<D2>(
self,
f: impl FnOnce(<E as IntoLanes>::Rejected) -> D2,
) -> Result<T, Fail<D2, <E as IntoLanes>::Lanes>>where
E: IntoLanes,
<E as IntoLanes>::Rejected: Rejection,
fn map_rejected<D2>(
self,
f: impl FnOnce(<E as IntoLanes>::Rejected) -> D2,
) -> Result<T, Fail<D2, <E as IntoLanes>::Lanes>>where
E: IntoLanes,
<E as IntoLanes>::Rejected: Rejection,
Maps the rejection with a closure and leaves every other lane as it
is: the Result-level form of Fail::map_rejected. For a
type-level remap use widen; this is for enriching a
rejection with data only the call site has, such as the input that
was attempted: repo.create(new).await.map_rejected(|r| r.with_attempted(id))?.
Sourcefn widen_via_builtin(
self,
) -> Result<T, <<E as IntoLanes>::Rejected as BuiltinFor<<E as IntoLanes>::Lanes>>::Builtin>where
E: IntoLanes,
<E as IntoLanes>::Rejected: BuiltinFor<<E as IntoLanes>::Lanes>,
fn widen_via_builtin(
self,
) -> Result<T, <<E as IntoLanes>::Rejected as BuiltinFor<<E as IntoLanes>::Lanes>>::Builtin>where
E: IntoLanes,
<E as IntoLanes>::Rejected: BuiltinFor<<E as IntoLanes>::Lanes>,
Lands the error in its own built-in, with no destination named:
Fault<L> for a source that never rejects, Fail<R, L> for one that
can, where L is exactly the source’s lanes. Works on every lane
source: a carrier (its Repr), a Classify wrapper, a bare
Rejection, a lane payload, or a Fault / Fail (identity).
Two uses:
- Into a
Box<dyn Error>. A wrapper’s classification lives in itsimpl Classify, not in the value, so?on the bare wrapper boxes it unlaned and the receivingFault::classifywalks past it to the foreign error underneath. Through its built-in, the box holds the lane payload, with the wrapper and its own source still in the chain, so what the boundary records (kind aserror.code,error.level,exception.message) is exactly what recording the built-in directly gives. A rejection does not survive a box this way or any other:Fault::classifyhas no rejected arm. Resolve rejections before boxing. - Into a carrier from another crate.
?cannot convert one crate’s carrier into another crate’s carrier, but every carrier absorbs a built-in, so.widen_via_builtin()?reaches it in two hops.
The built-in has exactly the source’s lanes, the narrowest it can
become, so any destination that would accept a wider one accepts it.
It is not needed for a built-in Fault destination: a wrapper or a
carrier already ?s straight into one, and Fault to Fault is never
a ? (that is .widen()).
use errlanes::{Fault, FatalKind, ResultExt};
#[derive(Debug, errlanes::Classify)]
#[classify(fatal(CorruptState))]
#[error("stored bytes do not decode")]
struct Undecodable(#[source] std::io::Error);
fn decode() -> Result<u8, Undecodable> {
Err(Undecodable(std::io::Error::other("bad bytes")))
}
fn boundary() -> Result<u8, Box<dyn std::error::Error + Send + Sync>> {
Ok(decode().widen_via_builtin()?)
}
let boxed = boundary().unwrap_err();
assert!(matches!(Fault::classify(&*boxed), Fault::Fatal(f) if f.kind == FatalKind::CorruptState));Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".