#Outcome Patterns Cookbook

Version: 0.43.0 | Updated: 2026-03-25 | Applies to: ranvier-core 0.42+ | Category: Cookbook


#Overview

Outcome<T, E> is Ranvier's control flow type. v0.42 introduced combinators and the try_outcome! macro to reduce boilerplate when converting between Result and Outcome. This cookbook covers all ergonomic patterns.

Example: outcome-patterns (in examples/outcome-patterns/)


#1. `try_outcome!` Macro

Converts Result<T, E> into early-return Outcome::Fault. Two forms:

#Form 1: Bare conversion

use ranvier_core::{prelude::*, try_outcome};

let pool = try_outcome!(bus.get_cloned::<PgPool>());
// On Err: returns Outcome::Fault(e.to_string())

#Form 2: With context string

let pool = try_outcome!(bus.get_cloned::<PgPool>(), "PgPool not in Bus");
// On Err: returns Outcome::Fault("PgPool not in Bus: <error>")

#Before / After

// BEFORE (v0.41): manual match
let pool = match bus.get_cloned::<PgPool>() {
    Ok(p) => p,
    Err(e) => return Outcome::Fault(e.to_string()),
};

// AFTER (v0.42+): try_outcome!
let pool = try_outcome!(bus.get_cloned::<PgPool>());

Import Note: try_outcome! is NOT in the prelude. Use use ranvier_core::try_outcome;


#2. `Outcome::from_result()`

Converts Result<T, E: Display> into Outcome<T, String>:

let outcome: Outcome<i64, String> = Outcome::from_result("42".parse::<i64>());
// Ok(42) โ†’ Outcome::Next(42)
// Err(e) โ†’ Outcome::Fault(e.to_string())

Best for one-shot conversion when you need the full Outcome value (not early-return).


#3. Combinator Chain: `and_then`

Monadic bind โ€” chain Outcome-returning closures:

let result = Outcome::<i64, String>::Next(10)
    .and_then(|n| {
        if n > 0 {
            Outcome::Next(n * 2)
        } else {
            Outcome::Fault("must be positive".into())
        }
    })
    .and_then(|n| Outcome::Next(format!("result = {n}")));
// Next("result = 20")

Non-Next variants (Branch, Jump, Emit, Fault) pass through unchanged.


#4. `map` and `map_fault`

Transform the success value or the error value:

// map: transform Next value
let doubled = Outcome::<i64, String>::Next(5).map(|n| n * n);
// Next(25)

// map_fault: transform Fault value (error type conversion)
let typed_err = Outcome::<(), i32>::Fault(404)
    .map_fault(|code| format!("HTTP {code}"));
// Fault("HTTP 404")

#5. `unwrap_or`

Extract the Next value with a default for any other variant:

let value = some_outcome.unwrap_or(0);
// Next(42) โ†’ 42
// Fault(_) / Branch(_) / Jump(_) / Emit(_) โ†’ 0

#6. Pattern: Pipeline with Combinators

async fn process_order(
    &self, input: OrderInput, _res: &(), bus: &mut Bus,
) -> Outcome<OrderResult, String> {
    let pool = try_outcome!(bus.get_cloned::<PgPool>(), "DB unavailable");

    Outcome::from_result(
        sqlx::query_as("SELECT * FROM orders WHERE id = $1")
            .bind(input.order_id)
            .fetch_one(&pool)
            .await
            .map_err(|e| e.to_string())
    )
    .and_then(|order: Order| {
        if order.status == "cancelled" {
            Outcome::Fault("Order already cancelled".into())
        } else {
            Outcome::Next(order)
        }
    })
    .map(|order| OrderResult {
        id: order.id,
        total: order.total,
        status: order.status,
    })
}

#7. Decision Matrix

Situation Use
Result โ†’ early-return Fault try_outcome!(expr)
Result โ†’ early-return with context try_outcome!(expr, "context")
Result โ†’ full Outcome value Outcome::from_result(expr)
Chain dependent operations .and_then(|v| ...)
Transform success value .map(|v| ...)
Transform error type .map_fault(|e| ...)
Extract value with default .unwrap_or(default)

#See Also

  • Bus Access Patterns Cookbook โ€” Bus::get_cloned(), BusHttpExt
  • JSON Outcomes Cookbook โ€” typed JSON at route boundary
  • examples/outcome-patterns/ โ€” runnable demo of all patterns above