Exceptions
What the library throws, when it throws it, and what to write instead.
The point of this library is that failure is a value. So it throws rarely, and when it does, it is because you asked for a value that was not there.
There are two exception types, and both mean the same thing: you skipped the check.
UnwrapException
Thrown when you take the value out of a monad that does not have one.
Option<string> none = Option.None<string>();
none.Unwrap(); // throws UnwrapException
Result<int, string> err = Result.Err<int, string>("the ritual fizzled");
err.Unwrap(); // throws UnwrapExceptionAnd in the other direction, when you take the error out of something that succeeded:
Result<int, string> ok = Result.Ok<int, string>(20);
ok.UnwrapErr(); // throws UnwrapExceptionSo: Unwrap on a None or an Err, and UnwrapErr on an Ok.
The async versions — UnwrapAsync and UnwrapErrAsync — throw for exactly the same reasons.
What to write instead
Nine times out of ten you wanted one of these:
option.Unwrap()
option.Match(…, …)
Handles both cases, returns a plain value.
option.Unwrap()
option.UnwrapOr(fallback)
You already have something to fall back to.
option.Unwrap()
option.UnwrapOrElse(() => …)
The fallback costs something to build.
option.Unwrap()
option.Map(…)
You were going to keep working with it anyway.
Unwrap is not forbidden. It is the right call when absence really would be a bug and you want to fail loudly and immediately — the same reasoning as First on a sequence you know is not empty. The analyzer will tell you when you have reached for it out of habit.
UnmetExpectationException
Thrown for the same reasons, by Expect and ExpectErr rather than Unwrap and UnwrapErr. The difference is that you supply the message.
ExpectAsync and ExpectErrAsync behave the same way.
When to prefer Expect over Unwrap
Use Expect when the throw is deliberate and you can say something useful about why. The message goes straight into the exception, so whoever reads the log gets your reasoning rather than a stack trace and a shrug.
That makes Expect the better choice in a test, in application startup, and anywhere else the invariant is worth stating out loud. Unwrap is the terser option when there is nothing to add.
Exceptions the library catches
Separately from the two above, some operations swallow exceptions on purpose.
Option.Try, Result.Try and their async counterparts run a factory that might throw, and turn a throw into a None or an Err. That is the whole point of them.
Two things to know about that:
The exception is not lost. It goes to your configured exception logger. See Configuration and Observability.
OperationCanceledExceptionis let through. Cancellation is not a failure of your work, so it is not turned into anErr. See Configuration.
Turning an exception into an Error
The other direction. You caught something at a boundary, and you want it as a Result rather than a rethrow. Both error types convert.
The code is the exception's type name with a trailing Exception removed. There is no prefix, the suffix match ignores case, and the exception's message is never read — so nothing from its text reaches the code.
SqlException
Sql
InvalidOperationException
InvalidOperation
ScryingFailedException
ScryingFailed
TimeoutException
Timeout
Exception
Exception
Exception itself is the one special case. It keeps its whole name rather than reducing to an empty code.
ErrorCode.FromException does the same job when you want only the code:
This is a fallback, not a strategy. Codes derived from exception types drift out of step with the codes you define by hand, and they do not help at all for failures that were never exceptions. Reach for it at a boundary you do not control, not throughout your domain.
Exceptions from the constructors
One more, and it is not from this library's own hierarchy.
Neither side of a Result can hold null, and nor can a Some. The constructors enforce it:
A default value is fine — Result.Ok<int, string>(0) is an Ok holding zero. It is null specifically that is rejected. See Result<T, E>.
Where to go next
Errors — building the failure value you return.
Option<T> and Result<T, E> — the safe ways out of a monad.
Analyzer rules — what flags an
Unwrapyou should not have written.
Last updated
Was this helpful?