RustLikeValues 2.0.1
dotnet add package RustLikeValues --version 2.0.1
NuGet\Install-Package RustLikeValues -Version 2.0.1
<PackageReference Include="RustLikeValues" Version="2.0.1" />
<PackageVersion Include="RustLikeValues" Version="2.0.1" />
<PackageReference Include="RustLikeValues" />
paket add RustLikeValues --version 2.0.1
#r "nuget: RustLikeValues, 2.0.1"
#:package RustLikeValues@2.0.1
#addin nuget:?package=RustLikeValues&version=2.0.1
#tool nuget:?package=RustLikeValues&version=2.0.1
RustLikeValues
A comprehensive C# library that brings Rust's powerful Option<T>
and Result<T, E>
types to .NET, along with functional extensions and logging capabilities. Write safer, more expressive code by making nulls and errors explicit in your type system.
Overview
RustLikeValues provides a complete implementation of Rust's error handling patterns for C#, enabling you to:
- Eliminate null reference exceptions with
Option<T>
- Replace exceptions with explicit error handling using
Result<T, E>
- Chain operations with railway-oriented programming
- Log functionally with result-based logging
- Parse and transform data safely
Packages
The library is split into focused NuGet packages:
Package | Description | Install |
---|---|---|
RustLikeValues.Option |
Option type for null-safety | dotnet add package RustLikeValues.Option |
RustLikeValues.Result |
Result type for error handling | dotnet add package RustLikeValues.Result |
RustLikeValues.Extensions |
Extensions & RLogger | dotnet add package RustLikeValues.Extensions |
Quick Start
Option - Handling Optional Values
using RustLikeValues.RustLikeOption;
// No more null checks!
Option<User> user = GetUser(id); // Returns Option<User> instead of User?
var email = user
.Map(u => u.Email)
.Filter(e => e.Contains("@"))
.UnwrapOr("no-email@default.com");
// Pattern matching
var message = user.Match(
Some: u => $"Welcome, {u.Name}!",
None: () => "User not found"
);
// Implicit conversion from values
Option<int> some = 42; // Automatically Some(42)
Option<int> none = Option.None;
Result - Explicit Error Handling
using RustLikeValues.RustLikeResult;
// Errors are values, not exceptions
Result<int, string> Divide(int a, int b)
{
if (b == 0)
return "Cannot divide by zero"; // Implicit Err
return a / b; // Implicit Ok
}
// Chain operations with automatic error propagation
var result = Divide(10, 2)
.AndThen(x => Divide(x, 2))
.Map(x => x * 10)
.MapErr(err => $"Calculation failed: {err}");
// Pattern matching
result.Match(
Ok: value => Console.WriteLine($"Result: {value}"),
Err: error => Console.WriteLine($"Error: {error}")
);
Extensions & Logging
using RustLikeValues.RustLikeExtension;
using RustLikeValues.Logging;
// Safe parsing
Option<int> number = "42".ParseInt();
Option<DateTime> date = "2024-01-01".ParseDateTime();
// Collection operations
var user = users.FindFirst(u => u.IsAdmin);
var value = dictionary.Get("key"); // Returns Option<T>
// Functional try-catch
var result = Try.Execute(() => JsonSerializer.Deserialize<Data>(json));
// Result-based logging
var logger = new RLogger<MyClass>();
logger.LogInfo($"Processing user {userId}");
// Log with Option/Result integration
var data = GetData(id);
if (logger.IfNone(data, "Data not found"))
return; // Early exit if None
Why RustLikeValues?
1. Null Safety with Option
// ❌ Traditional C# - NullReferenceException waiting to happen
public User? GetUser(int id) { ... }
var user = GetUser(1);
Console.WriteLine(user.Name); // 💥 Possible NullReferenceException
// ✅ With Option - Null safety enforced by type system
public Option<User> GetUser(int id) { ... }
var user = GetUser(1);
user.Match(
Some: u => Console.WriteLine(u.Name), // Only executes if user exists
None: () => Console.WriteLine("No user found")
);
2. Explicit Error Handling with Result
// ❌ Traditional C# - Exceptions are hidden
public Order ProcessOrder(OrderRequest request)
{
ValidateRequest(request); // Might throw ValidationException
var order = CreateOrder(request); // Might throw OutOfStockException
ChargePayment(order); // Might throw PaymentException
return order;
}
// ✅ With Result - All errors are visible in the signature
public Result<Order, OrderError> ProcessOrder(OrderRequest request)
{
return ValidateRequest(request)
.AndThen(CreateOrder)
.AndThen(ChargePayment); // Errors propagate automatically
}
3. Composable Operations
// Chain operations without nested if-statements or try-catch blocks
var finalResult = GetUser(userId)
.Map(user => user.Profile)
.Filter(profile => profile.IsActive)
.AndThen(profile => UpdateLastLogin(profile))
.MapErr(error => new ServiceError($"Failed to update user: {error}"))
.Tap(profile => logger.LogInfo($"Updated profile: {profile.Id}"))
.TapErr(error => logger.LogError(error.Message));
4. Smart Implicit Conversions
// Implicit conversions make the API natural to use
Result<User, string> GetUser(int id)
{
if (id < 0)
return "Invalid user ID"; // Automatically Err
var user = database.Find(id);
if (user == null)
return "User not found"; // Automatically Err
return user; // Automatically Ok
}
// Option also supports implicit conversion
Option<string> name = "John"; // Automatically Some("John")
Option<string> empty = Option.None;
Real-World Example
Here's a complete example showing how these types work together in a real application:
public class UserService
{
private readonly IUserRepository _repository;
private readonly RLogger<UserService> _logger;
public async Task<Result<UserDto, ApiError>> GetUserAsync(string email)
{
_logger.LogInfo($"Fetching user by email: {email}");
// Parse and validate email
var validEmail = email
.ToOption()
.Filter(e => e.Contains("@"))
.ToResult(new ApiError("Invalid email format"));
if (_logger.IfErr(validEmail, "Email validation failed"))
return validEmail.Map(e => new UserDto()); // Type conversion
// Fetch from database
var user = await validEmail
.AndThenAsync(async e => await _repository.FindByEmailAsync(e))
.AndThen(opt => opt.ToResult(new ApiError("User not found")));
// Transform and return
return user
.Map(u => new UserDto
{
Id = u.Id,
Email = u.Email,
Name = u.Name
})
.Tap(dto => _logger.LogInfo($"Successfully fetched user {dto.Id}"))
.TapErr(err => _logger.LogWarning($"Failed to fetch user: {err}"));
}
public Result<List<int>, ApiError> ParseUserIds(string input)
{
return Try.Execute(() =>
{
return input
.Split(',')
.Select(s => s.Trim())
.FilterMap(s => s.ParseInt()) // Safe parsing
.Where(id => id > 0)
.ToList();
})
.MapErr(ex => new ApiError($"Invalid ID format: {ex.Message}"));
}
}
Key Benefits
- Type Safety: Null and error cases are explicit in method signatures
- Composability: Chain operations without nesting or early returns
- Performance: Zero-cost abstractions with struct implementations
- Expressiveness: Code clearly shows intent and possible outcomes
- Reduced Bugs: Impossible to forget null checks or error handling
- Better APIs: Method signatures tell the whole story
License
This project is licensed under the MIT License - see the LICENSE file for details.
Make your C# code safer and more expressive with RustLikeValues!
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.
Version | Downloads | Last Updated |
---|