RustlikeValues.Result 1.0.0

There is a newer version of this package available.
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
                    
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="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RustlikeValues.Result" Version="1.0.0" />
                    
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 1.0.0
                    
#r "nuget: RustlikeValues.Result, 1.0.0"
                    
#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@1.0.0
                    
#: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=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=RustlikeValues.Result&version=1.0.0
                    
Install as a Cake Tool

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}"
    );
  • 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 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.

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