Skip to main content

ResultExt

Trait ResultExt 

Source
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§

Source

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).

Source

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()
}
Source

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.

Source

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()
}
Source

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();
}
Source

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))?.

Source

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 its impl Classify, not in the value, so ? on the bare wrapper boxes it unlaned and the receiving Fault::classify walks 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 as error.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::classify has 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));
Source

fn classify<W>(self) -> Result<T, W>
where W: Classify + From<E>,

.classify::<W>() — the verb that turns a foreign error into a local Classify wrapper at a one-off call site, so a function that does not itself return W can still enter the lanes through it: conn.query(..).await.classify::<DbWrite>()?.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl<T, E> ResultExt<T, E> for Result<T, E>

Source§

fn widen<E2>(self) -> Result<T, E2>
where Result<T, E>: WidenResult<T, E2>,

Source§

fn narrow_rejected(self) -> Result<T, <E as NarrowRejectedLane>::Narrowed>
where E: NarrowRejectedLane,

Source§

fn narrow_transient( self, attempts: u32, ) -> Result<T, <E as Laned>::WithoutTransient>
where E: Laned,

Source§

fn narrow_denied(self) -> Result<T, <E as NarrowDeniedLane>::Narrowed>
where E: NarrowDeniedLane,

Source§

fn rejected( self, ) -> Result<Result<T, <E as IntoLanes>::Rejected>, Fault<<E as IntoLanes>::Lanes>>
where E: IntoLanes, <E as IntoLanes>::Rejected: Rejection,

Source§

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,

Source§

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>,

Source§

fn classify<W>(self) -> Result<T, W>
where W: Classify + From<E>,

Implementors§