Table of Contents

Handle absence and failure

Raven uses two common carrier types to make expected control flow visible: Option<T> for meaningful absence and Result<T, E> for an operation that can succeed or fail with useful information.

Represent absence with Option

Return Some(value) when a value exists and None when absence is an expected outcome:

func FindPlan(code: string) -> Option<RatePlan> {
    return plans.FirstOrNone(plan => plan.Code == code)
}

let message = match FindPlan("standard") {
    Some(let plan) => "Found ${plan.Name}"
    None => "Plan not found"
}

Use nullable types at .NET boundaries when the framework contract calls for them. Convert to an option when absence has domain meaning inside the application.

Represent expected failure with Result

A result makes both the success value and error value part of the function's contract:

func ValidateWeight(weightKg: decimal) -> Result<decimal, string> {
    if weightKg <= 0 {
        return Error("Weight must be positive")
    }

    return Ok(weightKg)
}

Use exceptions for faults and framework contracts that are naturally exception-based. Use Result when callers are expected to inspect and handle the failure.

Propagate a failure with ?

Inside a compatible function, ? extracts the successful value or returns the failure immediately:

func BuildQuote(weightKg: decimal) -> Result<decimal, string> {
    let weight = ValidateWeight(weightKg)?
    return Ok(12.50m + weight * 1.75m)
}

Use match instead when the current function should recover, translate the error, or choose another path.

The operator is extensible: a custom class, struct, record, or union can opt in by implementing IPropagatable<TSelf, TOutput, TResidual>. The contract provides success extraction, residual extraction, and static reconstruction of an enclosing carrier from a residual. Result and Option use this same contract rather than compiler-only representation rules.

See Raven.Core for the available Option and Result operations.