RustyOptions 0.6.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package RustyOptions --version 0.6.0                
NuGet\Install-Package RustyOptions -Version 0.6.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="RustyOptions" Version="0.6.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RustyOptions --version 0.6.0                
#r "nuget: RustyOptions, 0.6.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.
// Install RustyOptions as a Cake Addin
#addin nuget:?package=RustyOptions&version=0.6.0

// Install RustyOptions as a Cake Tool
#tool nuget:?package=RustyOptions&version=0.6.0                

RustyOptions

Option and Result types for C#, inspired by Rust

CI CodeQL codecov

Avoid Null-Reference Errors

The C# nullable reference types feature is useful, but it's entirely optional, and the warnings it produces are easily ignored.

RustyOptions uses the type system to:

  • Make it impossible to access a possibly-missing value without first checking if the value is present.
  • Clearly express your intent in the code you build. If a method might not return a value, or might return an error message instead of a value, you can express this in the return type where it can't be missed.
  • Provide an expressive API that allows chaining and combination of optional or possibly-failed results.

Usage

Creating an Option

There are many ways to create an Option instance - use whichever appeals to you!

Option<int> ex1 = Option.Some(42);
Option<int> ex2 = 42.Some();
Option<int> ex3 = Option<int>.None;
Option<int> ex4 = Option.None<int>();
Option<int> ex5 = default; // equivalent to Option.None<int>()

int? maybeNull = GetPossiblyNullInteger();
Option<int> ex5 = Option.Create(maybeNull); // null turns into Option.None

// Or you can use 'using static' for more concise/F#/Rust-like syntax:
using static RustyOptions.Option;

var ex6 = Some(42);
var ex7 = None<int>();

Getting values from an Option

if (myOption.IsSome(out var value))
{
    // do something with the value
}

// Get the contained value if Some, or the default value for the contained type if None
var innerValue = myOption.UnwrapOr(default);

if (myOption.IsNone)
{
   // Do something because the option is None,
   // although in many cases the Unwrap/Map/And/Or
   // methods are more useful than checking IsNone directly.
}

Creating a Result

Result can also be created in a variety of ways.

// assumes an Err type of string
Result<int, string> ex1 = Result.Ok(42);
// assumes an Err type of Exception
Result<int, Exception> ex2 = Result.OkExn(42);
// fully specify Ok and Err types
Result<int, MyCustomException> ex3 = Result.Ok<int, MyCustomException>(42);

Result<int, string> ex4 = Result.Err<int>("Oops.");
Result<int, Exception> ex5 = Result.Err<int>(new Exception("Oops."));
Result<int, MyCustomException> ex6 = Result.Err<int, MyCustomException(someException);

// Or you can use 'using static' for more concise/F#/Rust-like syntax:
using static RustyOptions.Result;

var ex7 = Ok(42);
var ex8 = Err<int>("oops");

Getting values from a Result

if (myResult.IsOk(out var value))
{
    // do something with the value
}

if (myResult.IsErr(out var err))
{
    // do something with the error
}

Safely Chain Together Fallible Methods

RustyOptions has an extensive API that supports safely chaining together multiple methods that return Option or Result and supports easily converting between Option and Result if you have a mixture of such methods.

var output = Option.Parse<int>(userInput)
    .AndThen(ValidateRange)
    .OrElse(() => defaultsByGroup.GetValueOrNone(user.GroupId))
    .MapOr(PillWidget.Render, string.Empty);

The example above does the following:

  1. Attempts to parse the user input into an integer.
  2. If the parsing succeeds, passes the resulting number to the ValidateRange method, which returns Some(parsedInput) if the parsed input is within the valid range, or None if it falls outside the valid range.
  3. If either steps 1 or 2 fail, we attempt to do a dictionary lookup to get a default value using the current user's group ID.
  4. If at the end we have a value, we render it to a string. Otherwise, we set output to an empty string.

Uses Modern .NET Features

For performance and convenience:

  • Supports parsing any type that implements IParsable<T> or ISpanParsable<T> (.NET 7 and above only)
  • The NumericOption<T> type supports generic math for any contained type that implements INumber<T> (.NET 7 and above only)
  • Supports async and IAsyncEnumerable<T>.
  • Supports nullable type annotations.
  • Supports serialization and deserialization with System.Text.Json.
  • IEquatable<T> and IComparable<T> allow Option and Result types to be easily compared and sorted.
  • IFormattable and ISpanFormattable allow Option and Result to efficiently format their content.
  • Option and Result can be efficiently converted to ReadOnlySpan<T> or IEnumerable<T> for easier interop with existing code.
  • Convenient extension methods for working with dictionaries (GetValueOrNone), collections (FirstOrNone), enums (Option.ParseEnum) and more.
  • Supports explicit conversion to and from the F# Option, ValueOption, and Result types for easy interop.

FAQ

  • This library only supports .NET 6 and above. What about .NET Framework?
    • You may want to consider the Optional library for legacy framework support.
  • Why create this library if Optional already exists?
    • I prefer the Rust Option/Result API methods and wanted to replicate those in C#.
    • I wanted to take advantage of modern .NET features like ISpanParsable<T> and INumber<T>.
    • I think having distinct Option and Result types is more clear than having two different kinds of Option.
    • As of this writing, Optional hasn't been updated in five years.
  • What about F# Option and Result?
    • The RustyOptions.FSharp nuget package will allow you to convert between F# and RustyOptions types.
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on RustyOptions:

Package Downloads
RustyOptions.FSharp

F# support for RustyOptions

oml

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.10.1 2,779 12/28/2023
0.10.0 206 12/3/2023
0.9.0 380 4/29/2023
0.8.0 357 2/6/2023
0.7.0 382 2/1/2023
0.6.1 388 1/31/2023
0.6.0 377 1/24/2023
0.5.0 305 1/18/2023
0.4.0 338 1/17/2023
0.3.0 302 1/9/2023
0.2.0 328 1/3/2023
0.1.0 545 12/31/2022