#Outcome 패턴 쿡북

버전: 0.43.0 | 업데이트: 2026-03-25 | 적용 대상: ranvier-core 0.42+ | 카테고리: 쿡북


#개요

Outcome<T, E>는 Ranvier의 제어 흐름 타입입니다. v0.42에서 컴비네이터와 try_outcome! 매크로가 도입되어 ResultOutcome 간 변환 보일러플레이트가 크게 줄었습니다.

예제: outcome-patterns (examples/outcome-patterns/)


#1. `try_outcome!` 매크로

Result<T, E>를 조기 반환 Outcome::Fault로 변환합니다. 두 가지 형태:

#형태 1: 기본 변환

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

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

#형태 2: 컨텍스트 문자열 포함

let pool = try_outcome!(bus.get_cloned::<PgPool>(), "PgPool이 Bus에 없음");
// Err 시: Outcome::Fault("PgPool이 Bus에 없음: <에러>") 반환

#Before / After

// BEFORE (v0.41): 수동 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 주의: try_outcome!은 prelude에 포함되지 않습니다. use ranvier_core::try_outcome; 필요


#2. `Outcome::from_result()`

Result<T, E: Display>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())

#3. 컴비네이터 체인: `and_then`

모나딕 바인드 — Outcome을 반환하는 클로저 체이닝:

let result = Outcome::<i64, String>::Next(10)
    .and_then(|n| {
        if n > 0 { Outcome::Next(n * 2) }
        else { Outcome::Fault("양수여야 합니다".into()) }
    })
    .and_then(|n| Outcome::Next(format!("결과 = {n}")));
// Next("결과 = 20")

Next가 아닌 변형(Branch, Jump, Emit, Fault)은 그대로 전달됩니다.


#4. `map`과 `map_fault`

성공 값 또는 에러 값 변환:

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

// map_fault: Fault 값 변환 (에러 타입 변환)
let typed_err = Outcome::<(), i32>::Fault(404)
    .map_fault(|code| format!("HTTP {code}"));
// Fault("HTTP 404")

#5. `unwrap_or`

Next 값을 추출하고 다른 변형에는 기본값 사용:

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

#6. 패턴: 컴비네이터를 활용한 파이프라인

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

    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("주문이 이미 취소됨".into())
        } else {
            Outcome::Next(order)
        }
    })
    .map(|order| OrderResult {
        id: order.id,
        total: order.total,
        status: order.status,
    })
}

#7. 결정 매트릭스

상황 사용
Result → 조기 반환 Fault try_outcome!(expr)
Result → 컨텍스트 포함 조기 반환 try_outcome!(expr, "컨텍스트")
Result → 전체 Outcome Outcome::from_result(expr)
의존적 연산 체이닝 .and_then(|v| ...)
성공 값 변환 .map(|v| ...)
에러 타입 변환 .map_fault(|e| ...)
기본값으로 추출 .unwrap_or(default)

#참고

  • Bus Access Patterns 쿡북Bus::get_cloned(), BusHttpExt
  • JSON Outcomes 쿡북 — 라우트 경계에서의 타입이 지정된 JSON
  • examples/outcome-patterns/ — 위 패턴 전체 실행 가능 데모