RustlikeValues.Result 2.0.1

dotnet add package RustlikeValues.Result --version 2.0.1
                    
NuGet\Install-Package RustlikeValues.Result -Version 2.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RustlikeValues.Result" Version="2.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RustlikeValues.Result" Version="2.0.1" />
                    
Directory.Packages.props
<PackageReference Include="RustlikeValues.Result" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RustlikeValues.Result --version 2.0.1
                    
#r "nuget: RustlikeValues.Result, 2.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RustlikeValues.Result@2.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RustlikeValues.Result&version=2.0.1
                    
Install as a Cake Addin
#tool nuget:?package=RustlikeValues.Result&version=2.0.1
                    
Install as a Cake Tool

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

  1. Use descriptive error types

    // ❌ Avoid generic errors
    Result<User, string> GetUser(int id);
    
    // ✅ Prefer specific error types
    Result<User, UserNotFoundError> GetUser(int id);
    
  2. 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
    }
    
  3. 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);
    
  4. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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
2.0.1 86 6/20/2025
2.0.0 145 6/19/2025
1.0.1 138 6/18/2025
1.0.0 144 6/18/2025