If you are a C# developer, you have likely tangled with exceptions. While powerful, exception-based control flow can sometimes make your error paths opaque and difficult to trace. Enter the world of functional programming and Rust-inspired error handling. Today, I want to introduce you to an exciting way to build robust ASP.NET Core Web APIs using the SharpAndRusty libraries.
A comprehensive sample project known as the Bookshelf API recently showcased how to effectively utilize these libraries in a real-world scenario. Let’s explore the key advantages of adopting these Rust-like constructs in your C# codebase, using excerpts directly from the project’s documentation.
Ditching Exception-Based Control Flow
One of the core philosophies demonstrated in the Bookshelf API is treating errors as values rather than exceptions. Instead of throwing errors and disrupting the control flow, operations return a Result<T, Error>. This approach makes error paths explicit and guarantees type safety at compile time.
Consider this repository method excerpt:
public async Task<Result<User, Error>> FetchById(int id, CancellationToken token = default)
{
var userOption = await _dbContext.Users.FirstOrNoneAsync(x => x.Id == id, token);
return userOption switch
{
Option<User>.Some(var user) => user,
Option<User>.None => Error.New($"User {id} not found", ErrorKind.NotFound),
_ => Error.New($"User {id} not found", ErrorKind.NotFound)
};
}
As you can see, the method gracefully handles missing data and potential errors without a single throw statement, wrapping the outcome safely in a Result.
Safe Null Handling with Option
Handling nullable values is a notorious source of bugs. The Esox.SharpAndRusty core library provides the Option<T> type for safe handling of potentially missing values. By forcing the developer to explicitly unpack the Option, you eliminate the dreaded NullReferenceException.
For instance, when looking up a book, you can evaluate its existence safely:
var bookOption = await _dbContext.Books.FirstOrNoneAsync(x => x.Id == bookId, token);
if (bookOption is Option<Book>.None)
{
return Error.New($"Book {bookId} not found", ErrorKind.NotFound);
}
You may also employ the SharpAndRusty.Analyzer package, which will issue a warning if an option or result is not properly handled.
Composability and Railway-Oriented APIs
Chaining operations becomes incredibly composable using pattern matching and the Match() function. The Esox.SharpAndRusty.AspNetCore library allows for automatic error-to-HTTP response mapping. This means a specific ErrorKind like NotFound cleanly translates to a 404 status code, and InvalidInput becomes a 400 Bad Request.
Here is how elegant a controller endpoint can look using pattern matching:
[HttpGet("{id:int}")]
public async Task<IActionResult> GetById(int id) =>
(await repository.FetchById(id)).Match<IActionResult>(
success: book => Ok(mapper.ToResponse(book)),
failure: error => error.ToProblemDetails()
);
Join the Journey
The Bookshelf API is built with .NET 10, Entity Framework Core, and PostgreSQL, perfectly showcasing a clean architecture with clear separation between DTOs, entities, and business logic. It proves that you can bring Rust’s renowned reliability directly into modern C# web applications.
You can explore the full code, view the architecture, and run the sample yourself by visiting the repository: https://github.com/snoekiede/Esox.SharpAndRusty.Samples
What do you think? Have you tried using functional, Rust-like patterns in your C# projects? I would love to hear your thoughts—please drop your comments below!
Additionally, if you are interested in shaping the future of these libraries, contributors are highly encouraged to check out the project. Let’s bring more explicit, predictable, and reliable code to the C# ecosystem together!




