RustlikeValues.Result
1.0.0
See the version list below for details.
dotnet add package RustlikeValues.Result --version 1.0.0
NuGet\Install-Package RustlikeValues.Result -Version 1.0.0
<PackageReference Include="RustlikeValues.Result" Version="1.0.0" />
<PackageVersion Include="RustlikeValues.Result" Version="1.0.0" />
<PackageReference Include="RustlikeValues.Result" />
paket add RustlikeValues.Result --version 1.0.0
#r "nuget: RustlikeValues.Result, 1.0.0"
#:package RustlikeValues.Result@1.0.0
#addin nuget:?package=RustlikeValues.Result&version=1.0.0
#tool nuget:?package=RustlikeValues.Result&version=1.0.0
RustlikeValues.Result
A Rust-inspired Result<T, E>
type for C# that enables railway-oriented programming and explicit error handling without exceptions.
📦 Installation
dotnet add package RustlikeValues.Result
Dependencies: This package automatically includes RustlikeValues.Option
.
🎯 What is Result<T, E>?
The Result<T, E>
type represents either success or failure:
- Ok(T) - contains a success value of type
T
- Err(E) - contains an error value of type
E
This enables explicit error handling, railway-oriented programming, and eliminates the need for try-catch blocks in many scenarios.
🚀 Quick Start
using RustLikeValues.RustLikeResult;
// Creating Results
var success = Result.Ok<int, string>(42);
var failure = Result.Err<int, string>("Something went wrong");
// Railway-oriented programming
var result = ParseNumber("123")
.Map(x => x * 2) // Transform if Ok
.AndThen(x => Divide(x, 2)) // Chain operations
.UnwrapOr(0); // Get value or default
// result = 123
📚 Core API
Creation
// Create Ok variant
var success = Result.Ok<int, string>(42);
// Create Err variant
var failure = Result.Err<int, string>("Error message");
// Type inference often works
Result<int, string> ParseInt(string input)
{
if (int.TryParse(input, out var value))
return Result.Ok<int, string>(value);
else
return Result.Err<int, string>($"Cannot parse '{input}'");
}
Checking State
var result = Result.Ok<int, string>(42);
if (result.IsOk)
{
Console.WriteLine("Success!");
}
if (result.IsErr)
{
Console.WriteLine("Failed!");
}
// Implicit conversion to bool (true if Ok)
if (result)
{
Console.WriteLine("Success!");
}
Getting Values
var result = Result.Ok<int, string>(42);
// Get Ok value or throw
var value = result.Unwrap(); // 42
// Get Ok value or use default
var value = result.UnwrapOr(0); // 42
// Get Ok value or compute from error
var value = result.UnwrapOrElse(err => err.Length); // 42
// Get error value or throw
var error = result.UnwrapErr(); // throws InvalidOperationException
Transformations
var result = Result.Ok<int, string>(42);
// Map: transform Ok value, leave Err unchanged
var doubled = result.Map(x => x * 2); // Ok(84)
// MapErr: transform Err value, leave Ok unchanged
var mapped = result.MapErr(err => $"Error: {err}"); // Still Ok(42)
// AndThen: chain operations that return Results
var chained = result.AndThen(x =>
x > 0 ? Result.Ok<string, string>(x.ToString())
: Result.Err<string, string>("Not positive"));
Async Operations
var result = Result.Ok<string, string>("https://api.example.com");
// Async map
var response = await result.MapAsync(async url =>
await httpClient.GetStringAsync(url));
// Async MapErr
var handled = await result.MapErrAsync(async err =>
await LogErrorAsync(err));
// Async AndThen
var processed = await result.AndThenAsync(async data =>
{
var processed = await ProcessDataAsync(data);
return Result.Ok<ProcessedData, string>(processed);
});
Combining Results
var result1 = Result.Ok<int, string>(10);
var result2 = Result.Err<int, string>("Error");
// Use first result or try second
var combined = result1.Or(result2); // Ok(10)
// Compute alternative if Err
var alternative = result2.OrElse(err =>
Result.Ok<int, string>(0)); // Ok(0)
Converting to Option
var result = Result.Ok<int, string>(42);
// Extract Ok value as Option (method that returns Option<T>)
var okOption = result.Ok(); // Some(42)
// Extract Err value as Option (method that returns Option<E>)
var errOption = result.Err(); // None
var errorResult = Result.Err<int, string>("failed");
var errorOption = errorResult.Err(); // Some("failed")
var okFromError = errorResult.Ok(); // None
🎭 Pattern Matching
var result = Result.Ok<int, string>(42);
// Match with return value
var message = result.Match(
Ok: value => $"Success: {value}",
Err: error => $"Failed: {error}"
);
// Match with side effects
result.Match(
Ok: value => Console.WriteLine($"Got: {value}"),
Err: error => Console.WriteLine($"Error: {error}")
);
// Deconstruction
var (isOk, value, error) = result;
if (isOk)
{
Console.WriteLine($"Success: {value}");
}
else
{
Console.WriteLine($"Error: {error}");
}
🔧 Railway-Oriented Programming
Chain operations that can fail without nested try-catch blocks:
public Result<User, string> GetUser(int id) => /* ... */;
public Result<Profile, string> GetProfile(User user) => /* ... */;
public Result<string, string> GetAvatarUrl(Profile profile) => /* ... */;
// Chain operations - stops at first error
var result = GetUser(userId)
.AndThen(GetProfile)
.AndThen(GetAvatarUrl)
.Map(url => $"Avatar: {url}")
.UnwrapOr("No avatar available");
// With validation
var validatedResult = GetUser(userId)
.AndThen(user => user.IsActive
? Result.Ok<User, string>(user)
: Result.Err<User, string>("User is inactive"))
.AndThen(GetProfile)
.Map(profile => profile.DisplayName);
📄 JSON Serialization
Results serialize with a structured format:
public class ApiResponse<T>
{
public Result<T, string> Data { get; set; }
}
var response = new ApiResponse<int>
{
Data = Result.Ok<int, string>(42)
};
var json = JsonSerializer.Serialize(response);
// {"Data":{"$type":"ok","value":42}}
var errorResponse = new ApiResponse<int>
{
Data = Result.Err<int, string>("Not found")
};
var errorJson = JsonSerializer.Serialize(errorResponse);
// {"Data":{"$type":"err","error":"Not found"}}
🏃♂️ Performance
- Zero allocation overhead for error paths
- Minimal boxing - values stored directly in struct
- Aggressive inlining for hot path methods
- Struct-based design for stack allocation
📖 Examples
Safe File Operations
public Result<string, string> ReadFile(string path)
{
try
{
var content = File.ReadAllText(path);
return Result.Ok<string, string>(content);
}
catch (Exception ex)
{
return Result.Err<string, string>(ex.Message);
}
}
// Usage
var content = ReadFile("config.json")
.AndThen(ParseJson)
.Map(config => config.DatabaseUrl)
.UnwrapOr("default-connection-string");
API Client with Error Handling
public async Task<Result<WeatherData, ApiError>> GetWeatherAsync(string city)
{
try
{
var response = await httpClient.GetAsync($"/weather/{city}");
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadFromJsonAsync<WeatherData>();
return Result.Ok<WeatherData, ApiError>(data);
}
else
{
return Result.Err<WeatherData, ApiError>(
new ApiError(response.StatusCode, "Weather not found"));
}
}
catch (Exception ex)
{
return Result.Err<WeatherData, ApiError>(
new ApiError(0, ex.Message));
}
}
// Usage
var weather = await GetWeatherAsync("Stockholm")
.Map(data => $"{data.Temperature}°C")
.UnwrapOr("Weather unavailable");
Validation Pipeline
public Result<User, ValidationError> ValidateUser(UserInput input)
{
return ValidateEmail(input.Email)
.AndThen(_ => ValidateAge(input.Age))
.AndThen(_ => ValidatePassword(input.Password))
.Map(_ => new User(input.Email, input.Age));
}
public Result<string, ValidationError> ValidateEmail(string email)
{
if (string.IsNullOrEmpty(email))
return Result.Err<string, ValidationError>(
new ValidationError("Email is required"));
if (!email.Contains("@"))
return Result.Err<string, ValidationError>(
new ValidationError("Invalid email format"));
return Result.Ok<string, ValidationError>(email);
}
Database Operations
public Result<User, DatabaseError> CreateUser(string email, string name)
{
return ValidateInput(email, name)
.AndThen(_ => CheckUserExists(email))
.AndThen(_ => SaveToDatabase(email, name))
.Map(id => new User(id, email, name));
}
// Chain multiple database operations
var result = CreateUser("john@example.com", "John")
.AndThen(user => AddToGroup(user, "Users"))
.AndThen(user => SendWelcomeEmail(user.Email))
.Match(
Ok: user => $"User {user.Name} created successfully",
Err: error => $"Failed to create user: {error.Message}"
);
🔗 Related Packages
- RustlikeValues.Option - For nullable value handling (automatically included)
- RustlikeValues.Extensions - Additional utility methods for Result and Option
- RustlikeValues - Meta-package containing all types
🤝 Contributing
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
📄 License
This project is licensed under the MIT License.
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
- RustlikeValues.Option (>= 1.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.