Result<T, E>
Return failure as a value instead of throwing it, and chain fallible work.
Result<T, E> says the work might fail. It has exactly two shapes:
Ok<T, E>— it worked, and here is the value.Err<T, E>— it failed, and here is why.
Where Option<T> models absence, Result<T, E> models failure. The difference is the E. An Option tells you nothing arrived. A Result tells you what went wrong.
Why not just throw?
Exceptions are control flow with a bomb strapped to it.
They are invisible in a signature.
Reward ClaimReward(Quest)looks total.They are easy to forget. Nothing makes you handle one.
They do not compose. You cannot chain a method that throws.
They are catastrophic in a chain. One throw unwinds everything above it.
You cannot tell which methods throw without reading their source. A Result puts the failure in the return type, where you were already looking.
There is a second point, and it matters more. Err is not an emergency. When you write:
Result<Character, Error> FindCharacter(string name);you are saying "this can fail, and here is what failure looks like". That is a statement about your domain, not a panic button. Keep exceptions for the things that really are exceptional.
Create one
Leave TErr off and it defaults to the library's own Error type:
See Errors for what Error holds and how to build one from an enum.
Neither side can hold null
TOk and TErr are both constrained notnull, and the constructors enforce it. The guard arrived in 5.5.0 — before it, an Ok could hold null, and the null surfaced later as a NullReferenceException somewhere in your own code.
A default value is fine, though. It is a real value:
Chain fallible steps
AndThen is the workhorse. Each step returns a Result, and the first Err short-circuits the rest.
If FindCharacter fails, GetQuest and ClaimReward never run, and the original error arrives at the caller untouched. No try/catch. No special cases.
Map is the same idea when the next step cannot fail — it changes the Ok value and leaves an Err alone.
Work on the error side
This is what separates a Result from an Option, so it is worth knowing well.
MapErr
MapErr transforms the error, leaving the success value alone. Reach for it when two pieces of code disagree about the error type and you need them to chain.
The last two lines are a Map that nests, then a Flatten that undoes it. That is exactly what AndThen does in one step:
Prefer the second. The first is shown because you will meet it in code that grew one method at a time.
InspectErr
InspectErr runs a side effect on the error and hands the result back unchanged, so the chain continues. Logging is what it is for.
Inspect is the same thing on the Ok branch. Use both together and each log line runs only on its own track:
UnwrapErr and ExpectErr
These pull the error out and throw if the result was actually Ok.
ExpectErr does the same, but you supply the message:
Both are intentional points of failure, like First on an empty sequence. Use them when you have already established the result is an Err — in a test, say. Everywhere else, use Match.
Get the value back out
Match
Both branches, one plain value out. This is the default answer.
Unwrap with a fallback
UnwrapOrElse gets the error, so you can decide the fallback based on what went wrong. It only runs on an Err.
Pattern matching
Since 7.0.0, Result<T, E> deconstructs:
And exhaustively:
Check without unwrapping
IsOkAnd— it succeeded and the value passes the predicate.IsErrAnd— it failed and the error passes the predicate.
Combine two results
And and Or take a result you already have. AndThen and OrElse take a function, so the second result is only built when it is needed.
And and Or evaluate their argument eagerly. If producing it costs anything, use AndThen or OrElse instead.
And gives you the first Err, or the last Ok:
Ok1
Ok2
Ok2
Ok
Err
Err
Err
Ok
Err
Err1
Err2
Err1
Or gives you the first Ok, or the last Err:
Ok1
Ok2
Ok1
Ok
Err
Ok
Err
Ok
Ok
Err1
Err2
Err2
OrElse is how you recover. Its function runs on the error, not the value:
Work with a collection of them
A sequence of Result has its own helpers — Collect, Partition, Flatten, FlattenErr and AsEnumerable. They live in Waystone.Monads.Results.Extensions, and are covered on the Result<T, E> collections reference.
The short version:
Collect— all or nothing. OneErrfails the whole batch.Partition— keeps both sides, so you learn which ones failed.
For LINQ query syntax over a Result, see Waystone.Monads.Linq.
Printing and logging
ToString() never shows the wrapped value. You get the state and nothing else:
Result<TOk, TErr> is a record and both sides keep their value in a private property, so the compiler-generated ToString() has nothing to print. Interpolating a result into a log message tells you which branch you are on, never what it holds. Use the Inspect / InspectErr pair above instead.
When to reach for it
Use Result<T, E> when:
A function can fail and you want that visible in the signature.
You care why it failed.
You want the caller to handle the failure explicitly.
You are parsing, validating, or transforming input you do not control.
You want exceptions to mean something has genuinely gone wrong.
Reach for Option<T> instead when you do not care about the reason.
Where to go next
Option<T> — the same idea, for absence.
Errors — the
Errortype, error codes, and building one from an exception.Exceptions — what the library throws, and when.
Async — keeping a chain intact across an
await.Result<T, E> API — every overload, when you need one this page did not show.
Last updated
Was this helpful?