Introduction: Bye-Bye Exceptions, Hello Type Safety 👋
Welcome to the first part of our series on Esox.SharpAndRusty, a C# library designed to bring the safety and clarity of Rust’s Result<T, E> type to the modern .NET ecosystem.
In traditional C# programming, we often rely on exceptions for control flow when an operation might fail. While exceptions are crucial for truly exceptional events (like a system crash or running out of memory), relying on them for expected failure cases (like a “record not found” or “invalid input”) has significant drawbacks:
- Non-Explicit Failures: The caller has no compile-time indication of which errors a method might throw just by looking at the signature.
- Performance Overhead: Throwing and catching exceptions is computationally expensive and can hurt performance when used for regular control flow.
- Complex Flow: Failures often break the natural flow of your code, making logic harder to follow.
SharpAndRusty solves these problems by providing a type-safe and explicit way to handle operations that can either succeed or fail. Instead of throwing an exception, a function returns a Result<T, E>:
Tis the type of the success value (the outcome you want, e.g., aUserobject).Eis the type of the error value (what went wrong, e.g., astringerror message).
Implemented as a readonly struct, this approach is designed to be Zero Overhead for optimal performance, contrasting sharply with the cost of exceptions.
The Core Mechanism: Creation and Inspection 🔍
The heart of the library is the Result<T, E> type. It forces you to explicitly represent the state of your operation, making your code’s intent clear at a glance.
Static Factory Methods
Creating a result is straightforward and intentional using static factory methods:
- Success: Use
Result<T, E>.Ok(value). - Failure: Use
Result<T, E>.Err(error).
C#
using Esox.SharpAndRusty.Types;
// Example 1: Operation Succeeded
var success = Result<int, string>.Ok(42);
// Output: Ok(42)
// Example 2: Operation Failed
var failure = Result<int, string>.Err("User account is inactive");
// Output: Err(User account is inactive)
State Checking
You can then inspect the state of the result using simple boolean properties:
C#
if (success.IsSuccess)
{
Console.WriteLine("Operation succeeded! Proceed to next step.");
}
if (failure.IsFailure)
{
Console.WriteLine("Operation failed! Log error or show message.");
}
This explicit structure means you get type safety and a compile-time guarantee that you’ve considered the failure path.
Essential Handling: Pattern Matching and Safe Value Extraction 🔐
The library offers several idiomatic ways to consume a Result once you have it.
Pattern Matching (Match)
The most elegant and safest way to deal with a Result is through the Match method. It requires you to provide two lambda functions—one for the success state and one for the failure state—ensuring you deal with both outcomes explicitly and exhaustively:
C#
// The return type of Match is the return type of the lambdas (e.g., string)
public string ProcessUserLookup(Result<User, string> result)
{
return result.Match(
success: user => $"Welcome, {user.Name}! Your ID is {user.Id}.", // Executes if Ok
failure: error => $"Error: Could not retrieve user. Reason: {error}" // Executes if Err
);
}
Safe Value Extraction
For scenarios where you need the inner value, but want to avoid throwing exceptions, SharpAndRusty provides several safe extraction methods:
TryGetValue(out var age): This aligns with the ‘Try’ pattern common in .NET, letting you check for success and extract the value simultaneously.UnwrapOr(defaultValue): If the result is an error, this method returns the provided default value (e.g.,result.UnwrapOr(0)for an age).UnwrapOrElse(error => computeDefault): This is useful if computing a default value is complex or depends on the error itself. It computes the default value only when necessary (when an error occurs).Expect("message"): This method extracts the value but will throw anInvalidOperationExceptionwith your custom message if the result is an error. This should be used only when a failure is truly unrecoverable or indicates a major programming flaw.
Conclusion: Why use SharpAndRusty?
The decision to adopt Esox.SharpAndRusty is a choice to prioritize robustness, clarity, and performance in your C# applications. This library is more than just an exception-replacement—it’s a paradigm shift towards safer, more maintainable code.
The Core Benefits
- Explicit Error Handling: Method signatures clearly communicate potential failures, ensuring you never miss a critical error path. Your code becomes self-documenting in terms of success and failure outcomes.
- Avoid Exception Overhead: For anticipated failures (like failed parsing or database misses), you completely avoid the costly overhead of throwing exceptions, keeping your performance optimal.
- Eliminate Null References: By using
Result<T, E>, you make errors and missing data explicit, dramatically reducing the risk of unexpectedNullReferenceExceptionruntime errors. - Superior Composability: The structure of the
Resulttype is designed for functional composition, meaning complex multi-step operations (like processing an order that involves validation, payment, and database storage) can be chained together effortlessly, stopping and propagating the error at the first sign of failure.
Production-Ready Confidence
Allthough I consider the package a prototype; it’s designed for enterprise use:
- It features 123 comprehensive unit tests ensuring the API is stable and reliable.
- It includes built-in helpers for Exception Handling (
Try/TryAsync) to easily convert your legacy C# code to the saferResultpattern. - It has Full Async Support and LINQ query syntax support, making it a seamless fit for modern .NET applications.
By integrating SharpAndRusty, you are adopting the best practices of functional programming and Rust’s acclaimed error handling model, resulting in code that is easier to reason about, test, and deploy with confidence.
In Part 2, we will dive deeper into the Functional Composition features like Map and Bind, showing exactly how to build those powerful, linear, error-propagating chains we just talked about.




