RustlikeValues.Option
2.0.0
See the version list below for details.
dotnet add package RustlikeValues.Option --version 2.0.0
NuGet\Install-Package RustlikeValues.Option -Version 2.0.0
<PackageReference Include="RustlikeValues.Option" Version="2.0.0" />
<PackageVersion Include="RustlikeValues.Option" Version="2.0.0" />
<PackageReference Include="RustlikeValues.Option" />
paket add RustlikeValues.Option --version 2.0.0
#r "nuget: RustlikeValues.Option, 2.0.0"
#:package RustlikeValues.Option@2.0.0
#addin nuget:?package=RustlikeValues.Option&version=2.0.0
#tool nuget:?package=RustlikeValues.Option&version=2.0.0
RustLike Option<T> for C#
A complete implementation of Rust's Option type for C#, providing null-safe optional values with a rich functional API.
Installation
dotnet add package RustLikeValues.Option
Overview
The Option<T> type represents an optional value: every Option is either Some and contains a value, or None, and does not. It provides a null-safe alternative to nullable reference types with powerful functional programming capabilities.
Key Features
- Null Safety: Explicit handling of missing values
- Functional API: Map, Filter, AndThen, and more
- LINQ Support: Full integration with LINQ query syntax
- Pattern Matching: Works with C# pattern matching
- JSON Serialization: Built-in System.Text.Json support
- Zero Overhead: Struct-based for optimal performance
- IEnumerable: Treat as a collection of 0 or 1 elements
Quick Start
using RustLikeValues.RustLikeOption;
// Creating Options
var some = Option.Some(42);
var none = Option.None; // Global None instance
var empty = Option.Empty<int>(); // Typed None
// Null safety
string? nullable = GetNullableString();
Option<string> safe = nullable != null ? Option.Some(nullable) : Option.None;
// Pattern matching
var message = option.Match(
Some: value => $"Found: {value}",
None: () => "Not found"
);
// Chaining operations
var result = GetUser(id)
.Map(user => user.Email)
.Filter(email => email.Contains("@"))
.UnwrapOr("no-email@default.com");
Core Methods
Creation
Option.Some<T>(value)- Creates an Option containing a valueOption.None- Global None instance for any typeOption.Empty<T>()- Creates a typed NoneOption.TryCreate<T>(Func<T>)- Creates from a function, None on exception
Checking State
IsSome- Returns true if the Option contains a valueIsNone- Returns true if the Option is empty
Extracting Values
Unwrap()- Gets the value or throws if NoneUnwrapOr(defaultValue)- Gets the value or returns defaultUnwrapOrElse(Func<T>)- Gets the value or computes defaultGetValueOrDefault()- Returns value or default(T), can be null
Transforming
Map<U>(Func<T, U>)- Transform the contained valueMapAsync<U>(Func<T, Task<U>>)- Async transformationFilter(Func<T, bool>)- Keep value only if predicate is trueAndThen<U>(Func<T, Option<U>>)- Chain operations that return Option
Pattern Matching
Match<U>(Func<T, U> Some, Func<U> None)- Transform to a single typeMatch(Action<T> Some, Action None)- Execute side effects
Common Patterns
Null to Option Conversion
// Extension method approach
User? nullable = GetNullableUser();
Option<User> option = nullable.ToOption();
// Direct conversion
Option<string> name = username != null ? Option.Some(username) : Option.None;
// Implicit conversion
Option<int> number = 42; // Automatically Some(42)
Chaining Optional Operations
var email = GetUser(id)
.Map(user => user.Profile)
.Map(profile => profile.ContactInfo)
.Map(contact => contact.Email)
.Filter(email => IsValidEmail(email))
.UnwrapOr("no-email@example.com");
Working with Nullable Values
public Option<User> FindUserByEmail(string email)
{
// Database might return null if user doesn't exist
var user = database.Users.FirstOrDefault(u => u.Email == email);
return user != null ? Option.Some(user) : Option.None;
}
// Dictionary lookups
public Option<string> GetConfigValue(string key)
{
// Instead of checking TryGetValue
return configDictionary.TryGetValue(key, out var value)
? Option.Some(value)
: Option.None;
}
// Parsing that might not produce a value
public Option<Address> ParseAddress(string input)
{
if (string.IsNullOrWhiteSpace(input))
return Option.None;
var parts = input.Split(',');
if (parts.Length < 3)
return Option.None;
return Option.Some(new Address
{
Street = parts[0],
City = parts[1],
ZipCode = parts[2]
});
}
LINQ Integration
// Query syntax
var query = from user in GetUser(id)
from email in user.Email
where email.Contains("@company.com")
select email.ToUpper();
// Method syntax with multiple Options
var result = options
.Where(opt => opt.IsSome)
.Select(opt => opt.Unwrap())
.FirstOrDefault();
Collection Operations
// Option as IEnumerable
foreach (var value in option)
{
Console.WriteLine(value); // Executes 0 or 1 times
}
// LINQ operations
var doubled = option.Select(x => x * 2).FirstOrDefault();
Advanced Features
Global None Instance
// Use the global None for any Option type
Option<int> noNumber = Option.None;
Option<string> noString = Option.None;
// Useful for early returns
if (condition)
return Option.None;
Deconstruction
var (isSome, value) = option;
if (isSome)
ProcessValue(value);
JSON Serialization
var some = Option.Some(42);
var json = JsonSerializer.Serialize(some); // Output: 42
var none = Option.Empty<int>();
var noneJson = JsonSerializer.Serialize(none); // Output: null
// Deserialization
var decoded = JsonSerializer.Deserialize<Option<int>>("42"); // Some(42)
var missing = JsonSerializer.Deserialize<Option<int>>("null"); // None
Async Support
public async Task<Option<Data>> LoadDataAsync(string id)
{
var response = await httpClient.GetAsync($"/api/data/{id}");
if (!response.IsSuccessStatusCode)
return Option.None;
var data = await response.Content.ReadFromJsonAsync<Data>();
return Option.Some(data);
}
// Async chaining
var result = await GetDataAsync(id)
.MapAsync(async data => await ProcessAsync(data))
.AndThenAsync(async processed => await ValidateAsync(processed));
Flattening Nested Options
Option<Option<int>> nested = Option.Some(Option.Some(42));
Option<int> flattened = nested.Flatten(); // Some(42)
Best Practices
Prefer Option over nullable for domain models
public class User { public string Name { get; set; } // Required public Option<string> Nickname { get; set; } // Optional public Option<DateTime> LastLogin { get; set; } // Optional }Use Filter for validation
var validEmail = ParseEmail(input) .Filter(email => email.Contains("@")) .Filter(email => email.Length > 5);Chain operations instead of nested if-statements
// Instead of: if (option.IsSome) { var value = option.Unwrap(); if (IsValid(value)) { var result = Transform(value); return result; } } return defaultValue; // Use: return option .Filter(IsValid) .Map(Transform) .UnwrapOr(defaultValue);Use Match for exhaustive handling
option.Match( Some: value => database.Update(value), None: () => logger.LogWarning("No value to update") );
Comparison with Nullable
| Feature | Option<T> | Nullable (T?) |
|---|---|---|
| Explicit intent | ✅ Clear optional semantics | ❌ Can mean null or missing |
| Functional API | ✅ Rich set of operations | ❌ Limited operations |
| Chaining | ✅ Map, Filter, AndThen | ❌ Requires null checks |
| Pattern matching | ✅ Full support | ✅ Basic support |
| Reference types | ✅ Works with all types | ⚠️ Different behavior |
| Value types | ✅ Consistent API | ⚠️ Nullable<T> differences |
Performance
- Struct-based implementation with minimal overhead
- Aggressive inlining for common operations
- No heap allocations for the Option itself
- Suitable for high-performance scenarios
Migration Guide
// From nullable reference types
string? nullable = GetNullableString();
Option<string> option = nullable?.ToOption() ?? Option.None;
// From nullable value types
int? nullableInt = GetNullableInt();
Option<int> optionInt = nullableInt.HasValue
? Option.Some(nullableInt.Value)
: Option.None;
// From try-patterns
// Before:
if (dictionary.TryGetValue(key, out var value))
{
Process(value);
}
// After:
dictionary.Get(key) // Returns Option<T>
.Match(
Some: Process,
None: () => { }
);
License
MIT License - see LICENSE file for details
| 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.