Option, Result and propagation in Raven.
func Normalize(value: int) -> Result<int, OverflowError> {
let amount = Math.Abs(value)?
WriteLine("Continued")
return Result<int, OverflowError>.Ok(amount)
}Normalize(-42) produces Ok(42). The minimum Int32 value cannot be made positive, so the sample propagates OverflowError. It prints Continued and 42 for the first call, then Overflow propagated for the second.
Composing outcomes.
Development after Preview 8. These operators are implemented on main and need matching development references and the System library. They are not in the published Preview 8 packages; names and contracts remain open to feedback.
Map transforms a successful value. Then chains an operation that already returns an outcome. OrElse supplies a fallback only when needed. Patterns remain useful when you want to handle each case directly.
import System.*
import System.Collections.*
import System.Linq.*
import System.Option.*
import System.Result.*
import System.Console.*
func Main() {
let input: Option<int> = Some(21)
let answer = input.Map(value => value * 2)
.Filter(value => value > 0)
match answer {
Some(let value) => WriteLine(value)
None => WriteLine("No answer")
}
let result = answer.OkOr("Missing input")
.Then((value: int) -> Result<int, string> => Ok(value + 1))
match result {
Ok(let value) => WriteLine(value)
Error(let error) => WriteLine(error)
}
let absent: Option<int> = None
let fallback = absent.OrElse(() => Some(7))
WriteLine(fallback.UnwrapOr(0))
let failure: Result<int, string> = Error("Unavailable")
let recovered = failure.MapError(message => String.Concat("Read: ", message))
.OrElse(message => {
WriteLine(message)
return Ok(9)
})
WriteLine(recovered.UnwrapOr(0))
for value in answer.ToIterable() {
WriteLine(value)
}
}This example prints 42, 43, 7, Read: Unavailable, 9 and 42 on separate lines. The explicit Then callback signature works around a current compiler inference limitation; the other callbacks use inferred types. The recovery callback only runs for Error. ToIterable explicitly converts a successful value to a one-element collection; None and Error become empty collections.
Complete executable sample → · Expected output → · Development setup →
Errors are ordinary values.
Development after Preview 8. The legacy System.Error message wrapper is removed. Use a string for a simple message or a dedicated error type when callers need to distinguish cases. Result.Error is the union case; it does not require an error base class.
With imported Result cases and an expected type, write let failure: Result<int, string> = Error("Unavailable"), as in the tested example above. Rebuild callers with matching development references and libraries.
The current operator set.
| Operation | Option | Result |
|---|---|---|
| Transform a value | Map | Map |
| Chain an outcome | Then | Then |
| Keep a matching value | Filter | — |
| Transform an error | — | MapError |
| Recover with an outcome | OrElse (None) | OrElse (Error) |
| Extract with a fallback | UnwrapOr, UnwrapOrElse | UnwrapOr, UnwrapOrElse |
| Handle both branches | Match | Match |
| Run a branch action | Tap, TapNone | Tap, TapError |
| Convert to zero/one elements | ToIterable | ToIterable |
| Convert absence to an error | OkOr (value or factory), MapResult, ThenResult | — |
| Remove one nested layer | Flatten | — |
Callbacks run immediately on the matching branch. Tap methods return the original outcome. UnwrapOr takes an already evaluated fallback; UnwrapOrElse calls a parameterless factory only for None or Error. Use Result.OrElse or Match if the error itself is needed. ToIterable allocates a small collection and discards an Error payload, so use patterns when the error needs handling.
Raven.Core users will recognize these operators: neoCLR calls Where Filter and ToEnumerable ToIterable. Throwing unwrap helpers, generic-default helpers, nullable adapters, JSON integration and context-error support are outside this slice. Collection queries have their own .NET operator mapping.
Behavior and limits.
Unlike exception-based APIs in .NET, recoverable outcomes are in the return type. This makes handling explicit and changes caller code. Runtime faults still exist; Result does not convert every failure into a recoverable case.
Detailed contract and comparisons →PROPOSED DIRECTION
Where we’re heading.
Keep patterns and propagation consistent across the library. Broader async APIs may combine Task with Result, while cancellation and cleanup need their own contracts. No complete async model is implied by this working slice.
Related proposals and open questions →What would you try?
Bring a small use case, the code you tried and the behavior you expected. Which part of this contract helps, and which part should change?
Share feedback on GitHub ↗