RustlikeValues.Result
2.0.1
dotnet add package RustlikeValues.Result --version 2.0.1
NuGet\Install-Package RustlikeValues.Result -Version 2.0.1
<PackageReference Include="RustlikeValues.Result" Version="2.0.1" />
<PackageVersion Include="RustlikeValues.Result" Version="2.0.1" />
<PackageReference Include="RustlikeValues.Result" />
paket add RustlikeValues.Result --version 2.0.1
#r "nuget: RustlikeValues.Result, 2.0.1"
#:package RustlikeValues.Result@2.0.1
#addin nuget:?package=RustlikeValues.Result&version=2.0.1
#tool nuget:?package=RustlikeValues.Result&version=2.0.1
RustLike Result<T, E> for C#
A complete implementation of Rust's Result type for C#, providing explicit error handling as values instead of exceptions.
Installation
dotnet add package RustLikeValues.Result
Overview
Result<T, E>
represents either success (Ok
) with a value of type T
, or failure (Err
) with an error of type E
. This enables explicit error handling, making error cases visible in method signatures and enforcing their handling.
Key Features
- Explicit Error Handling: Errors are values, not hidden exceptions
- Smart Implicit Conversions: Automatic Ok/Err inference when types differ
- Railway-Oriented Programming: Chain operations with automatic error propagation
- Pattern Matching: Full support for C# pattern matching
- Zero Exceptions: Replace try-catch with functional error handling
- Type Safety: Compile-time guarantees about error handling
Quick Start
using RustLikeValues.RustLikeResult;
// Implicit conversions when T and E are different types
Result<int, string> Divide(int a, int b)
{
if (b == 0)
return "Cannot divide by zero"; // Implicit Err
return a / b; // Implicit Ok
}
// Pattern matching
var message = result.Match(
Ok: value => $"Success: {value}",
Err: error => $"Failed: {error}"
);
// Railway-oriented programming
var final = GetUser(id)
.AndThen(user => ValidateUser(user))
.Map(user => user.Email)
.MapErr(err => $"Operation failed: {err}")
.UnwrapOr("no-email@default.com");
Implicit Conversion Rules
The library provides smart implicit conversions:
When T and E are Different Types
Result<string, Exception> ProcessName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return new ArgumentException("Name is empty"); // Implicit Err
return name.ToUpper(); // Implicit Ok
}
Result<User, string> GetUser(int id)
{
if (id < 0)
return "Invalid user ID"; // Implicit Err
return new User { Id = id }; // Implicit Ok
}
When T and E are the Same Type
When types are ambiguous, you need to be explicit:
Result<string, string> Transform(string input)
{
if (string.IsNullOrEmpty(input))
return Result.Err("Input was empty"); // Explicit Err
return input.ToUpper(); // Implicit Ok (default for success)
}
Core API
Creation
// Implicit conversions (when T != E)
Result<int, string> r1 = 42; // Ok(42)
Result<int, string> r2 = "error"; // Err("error")
// Explicit creation (always works)
var r3 = Result.Ok(42); // OkValue<int>
var r4 = Result.Err("error"); // ErrValue<string>
var r5 = Result<int, string>.Ok(42); // Result<int, string>
var r6 = Result<int, string>.Err("error"); // Result<int, string>
// Type helpers for ambiguous cases
var r7 = Result.Success<int, string>(42); // Ok
var r8 = Result.Failure<int, string>("err"); // Err
Checking State
if (result.IsOk)
Console.WriteLine("Success!");
if (result.IsErr)
HandleError(result.UnwrapErr());
// Implicit bool conversion
if (result) // true if Ok
Process(result.Unwrap());
Extracting Values
// Unwrap - throws if wrong variant
var value = result.Unwrap(); // Throws if Err
var error = result.UnwrapErr(); // Throws if Ok
// Safe extraction
var value1 = result.UnwrapOr(defaultValue);
var value2 = result.UnwrapOrElse(err => ComputeDefault(err));
Transforming
// Map - transform Ok value
Result<string, Error> upperName = result.Map(name => name.ToUpper());
// MapErr - transform Err value
Result<int, string> withContext = result.MapErr(err => $"Context: {err}");
// AndThen - chain operations that return Result
Result<Order, Error> order = GetUser(id)
.AndThen(user => GetLatestOrder(user.Id))
.AndThen(order => ValidateOrder(order));
Pattern Matching
// Match with return value
string message = result.Match(
Ok: value => $"Success: {value}",
Err: error => $"Error: {error}"
);
// Match with side effects
result.Match(
Ok: value => Console.WriteLine($"Got: {value}"),
Err: error => logger.LogError(error)
);
// Deconstruction
var (isOk, value, error) = result;
if (isOk)
Process(value);
else
HandleError(error);
Common Patterns
Error Context/Wrapping
public Result<Config, string> LoadConfig(string path)
{
return ReadFile(path)
.MapErr(err => $"Failed to read config from {path}: {err}")
.AndThen(content => ParseJson<Config>(content))
.MapErr(err => $"Invalid config format: {err}");
}
Early Returns with Errors
public Result<Invoice, Error> ProcessOrder(Order order)
{
// Validate order
var validation = ValidateOrder(order);
if (validation.IsErr)
return validation.UnwrapErr(); // Early return with error
// Check inventory
var inventory = CheckInventory(order.Items);
if (inventory.IsErr)
return inventory.UnwrapErr();
// Create invoice
return CreateInvoice(order);
}
Collecting Results
public Result<List<User>, Error> GetAllUsers(List<int> ids)
{
var results = ids.Select(id => GetUser(id)).ToList();
// Check if any failed
var firstError = results.FirstOrDefault(r => r.IsErr);
if (firstError.IsErr)
return firstError.UnwrapErr();
// All succeeded - collect values
var users = results.Select(r => r.Unwrap()).ToList();
return users;
}
Try-Catch Replacement
// Instead of try-catch
public Result<Data, Exception> ParseData(string json)
{
try
{
var data = JsonSerializer.Deserialize<Data>(json);
return data ?? Result.Err(new Exception("Null result"));
}
catch (Exception ex)
{
return ex; // Implicit Err
}
}
// Or use Try extension
var result = Try.Execute(() => JsonSerializer.Deserialize<Data>(json));
Async Error Handling
public async Task<Result<User, ApiError>> GetUserAsync(int id)
{
var response = await httpClient.GetAsync($"/users/{id}");
if (!response.IsSuccessStatusCode)
return new ApiError(response.StatusCode); // Implicit Err
var json = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<User>(json);
return user ?? Result.Err(new ApiError("Empty response"));
}
// Async chaining
var result = await GetUserAsync(id)
.MapAsync(async user => await EnrichUserAsync(user))
.AndThenAsync(async user => await ValidateAsync(user));
Advanced Patterns
Railway-Oriented Programming
public Result<ProcessedOrder, OrderError> ProcessOrder(OrderRequest request)
{
return ValidateRequest(request)
.AndThen(CreateOrder)
.AndThen(CheckInventory)
.AndThen(CalculatePricing)
.AndThen(ApplyDiscounts)
.AndThen(ChargePayment)
.AndThen(UpdateInventory)
.Map(order => new ProcessedOrder(order))
.MapErr(error => new OrderError($"Order processing failed: {error}"));
}
Custom Error Types
public enum ValidationError
{
MissingField,
InvalidFormat,
OutOfRange
}
public Result<Email, ValidationError> ParseEmail(string input)
{
if (string.IsNullOrWhiteSpace(input))
return ValidationError.MissingField;
if (!input.Contains("@"))
return ValidationError.InvalidFormat;
return new Email(input); // Implicit Ok
}
Combining Results
public Result<Summary, Error> GenerateSummary(int userId)
{
var userResult = GetUser(userId);
var ordersResult = GetUserOrders(userId);
var prefsResult = GetUserPreferences(userId);
// If any failed, return first error
if (userResult.IsErr) return userResult.UnwrapErr();
if (ordersResult.IsErr) return ordersResult.UnwrapErr();
if (prefsResult.IsErr) return prefsResult.UnwrapErr();
// All succeeded
return new Summary(
userResult.Unwrap(),
ordersResult.Unwrap(),
prefsResult.Unwrap()
);
}
Best Practices
Use descriptive error types
// ❌ Avoid generic errors Result<User, string> GetUser(int id); // ✅ Prefer specific error types Result<User, UserNotFoundError> GetUser(int id);
Let implicit conversions work for you
Result<Data, Exception> LoadData(string path) { if (!File.Exists(path)) return new FileNotFoundException(path); // Implicit Err var content = File.ReadAllText(path); return ParseData(content); // Implicit Ok }
Chain operations instead of nested checks
// ❌ Avoid var r1 = Step1(); if (r1.IsErr) return r1.UnwrapErr(); var r2 = Step2(r1.Unwrap()); if (r2.IsErr) return r2.UnwrapErr(); return Step3(r2.Unwrap()); // ✅ Prefer return Step1() .AndThen(Step2) .AndThen(Step3);
Use Match for exhaustive handling
return result.Match( Ok: user => RenderUserProfile(user), Err: error => RenderErrorPage(error) );
Comparison with Exceptions
Aspect | Result<T, E> | Exceptions |
---|---|---|
Visibility | Errors in method signature | Hidden in implementation |
Performance | No stack unwinding | Stack unwinding overhead |
Composability | Chainable operations | Try-catch nesting |
Type safety | Compile-time checking | Runtime surprises |
Control flow | Explicit with returns | Implicit with throws |
Performance
- Value type (struct) implementation
- No heap allocations
- No exception overhead
- Inlined operations
- Suitable for hot paths
Quick Reference
Method | Description | Example |
---|---|---|
Ok(value) |
Create Ok result | Result.Ok(42) |
Err(error) |
Create Err result | Result.Err("failed") |
IsOk/IsErr |
Check variant | if (result.IsOk) |
Map(func) |
Transform Ok value | r.Map(x => x * 2) |
MapErr(func) |
Transform Err value | r.MapErr(e => $"Error: {e}") |
AndThen(func) |
Chain Result operations | r.AndThen(Validate) |
Match(ok, err) |
Pattern match | r.Match(v => v, e => 0) |
Unwrap() |
Get Ok value (throws if Err) | result.Unwrap() |
UnwrapOr(def) |
Get Ok or default | result.UnwrapOr(0) |
License
MIT License - see LICENSE file for details
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.