RustlikeValues.Option
2.0.1
See the version list below for details.
dotnet add package RustlikeValues.Option --version 2.0.1
NuGet\Install-Package RustlikeValues.Option -Version 2.0.1
<PackageReference Include="RustlikeValues.Option" Version="2.0.1" />
<PackageVersion Include="RustlikeValues.Option" Version="2.0.1" />
<PackageReference Include="RustlikeValues.Option" />
paket add RustlikeValues.Option --version 2.0.1
#r "nuget: RustlikeValues.Option, 2.0.1"
#:package RustlikeValues.Option@2.0.1
#addin nuget:?package=RustlikeValues.Option&version=2.0.1
#tool nuget:?package=RustlikeValues.Option&version=2.0.1
RustLike Option<T> for C#
A complete implementation of Rust's Option type for C#, providing explicit null-safety with a rich functional API.
Installation
dotnet add package RustLikeValues.Option
Overview
Option<T>
represents an optional value: every Option is either Some
and contains a value, or None
, and does not. This provides a null-safe alternative to nullable types with powerful functional programming capabilities.
Key Features
- Explicit Null Handling: No more NullReferenceException
- Implicit Conversions: Natural syntax with automatic wrapping
- Functional API: Map, Filter, AndThen for composable operations
- LINQ Support: Full integration with C# query syntax
- Pattern Matching: Works seamlessly with C# pattern matching
- Zero Overhead: Struct-based implementation
- JSON Support: Built-in System.Text.Json serialization
Quick Start
using RustLikeValues.RustLikeOption;
// Creating Options
Option<int> some = 42; // Implicit conversion
Option<string> none = Option.None; // Global None instance
Option<User> maybe = Option.Some(user); // Explicit Some
// 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 API
Creation
// Implicit conversion from value
Option<int> opt1 = 42;
// Explicit Some
Option<string> opt2 = Option.Some("hello");
// None variants
Option<int> opt3 = Option.None; // Global None
Option<int> opt4 = Option.Empty<int>(); // Typed None
Option<int> opt5 = default; // Default is None
// From nullable
string? nullable = GetNullableString();
Option<string> opt6 = nullable?.ToOption() ?? Option.None;
Checking State
if (option.IsSome)
Console.WriteLine("Has value");
if (option.IsNone)
Console.WriteLine("No value");
// Implicit bool conversion
if (option) // true if Some
Process(option.Unwrap());
Extracting Values
// Unwrap - throws if None
var value = option.Unwrap();
// Safe unwrapping
var value1 = option.UnwrapOr(defaultValue);
var value2 = option.UnwrapOrElse(() => ComputeDefault());
var value3 = option.GetValueOrDefault(); // Returns default(T)
Transforming
// Map - transform the value if Some
Option<string> email = user.Map(u => u.Email);
// Filter - keep only if predicate is true
Option<int> positive = number.Filter(n => n > 0);
// AndThen - chain operations that return Option
Option<Order> order = user
.AndThen(u => GetLatestOrder(u.Id))
.AndThen(o => ValidateOrder(o));
Pattern Matching
// Match with return value
string result = option.Match(
Some: value => $"Value is {value}",
None: () => "No value"
);
// Match with side effects
option.Match(
Some: value => Console.WriteLine(value),
None: () => Console.WriteLine("Empty")
);
// Deconstruction
var (isSome, value) = option;
if (isSome)
Process(value);
Common Patterns
Null to Option Conversion
public Option<User> FindUser(int id)
{
var user = database.GetUser(id); // Returns User?
return user?.ToOption() ?? Option.None;
}
// Extension method for nullable types
User? nullable = GetNullableUser();
Option<User> option = nullable.ToOption();
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");
Early Returns
public Option<ProcessResult> ProcessUser(int userId)
{
var user = GetUser(userId);
if (user.IsNone)
return Option.None; // Early return
var validated = ValidateUser(user.Unwrap());
if (validated.IsNone)
return Option.None;
return Option.Some(Process(validated.Unwrap()));
}
LINQ Integration
// Query syntax
var email = from user in GetUser(id)
from profile in user.Profile
where profile.IsActive
select profile.Email;
// Method chaining with LINQ
var activeEmails = users
.Select(u => u.Email)
.Where(opt => opt.IsSome)
.Select(opt => opt.Unwrap());
As IEnumerable
// Option implements IEnumerable<T>
foreach (var value in option)
{
Console.WriteLine(value); // Executes 0 or 1 times
}
// Use with LINQ
var doubled = option.Select(x => x * 2).FirstOrDefault();
Advanced Usage
Async Operations
public async Task<Option<Data>> LoadDataAsync(string id)
{
if (string.IsNullOrEmpty(id))
return Option.None;
var data = await api.GetDataAsync(id);
return data != null ? Option.Some(data) : Option.None;
}
// Async chaining
var result = await GetUserAsync(id)
.MapAsync(async user => await enrichUserDataAsync(user))
.AndThenAsync(async user => await ValidateAsync(user));
JSON Serialization
var some = Option.Some(42);
var json = JsonSerializer.Serialize(some); // "42"
var none = Option<int>.None;
var json2 = JsonSerializer.Serialize(none); // "null"
// Deserialization
var opt1 = JsonSerializer.Deserialize<Option<int>>("42"); // Some(42)
var opt2 = JsonSerializer.Deserialize<Option<int>>("null"); // None
Try Pattern
// Safe factory function
var option = Option.TryCreate(() => {
// Code that might throw
return riskyOperation();
}); // Returns None if exception occurs
Zip Operations
var name = Option.Some("John");
var age = Option.Some(30);
var person = name.Zip(age); // Option<(string, int)>
// Result: Some(("John", 30))
// If either is None, result is None
var missing = name.Zip(Option<int>.None); // None
Best Practices
Use Option for optional values in domain models
public class User { public string Name { get; set; } // Required public Option<string> Nickname { get; set; } // Optional public Option<DateTime> BirthDate { get; set; } // Optional }
Prefer chaining over nested if-statements
// ❌ Avoid if (opt1.IsSome) { var value1 = opt1.Unwrap(); var opt2 = Process(value1); if (opt2.IsSome) { var value2 = opt2.Unwrap(); return Transform(value2); } } return defaultValue; // ✅ Prefer return opt1 .AndThen(Process) .Map(Transform) .UnwrapOr(defaultValue);
Use Filter for validation
var validEmail = ParseEmail(input) .Filter(email => email.Contains("@")) .Filter(email => email.Length > 5);
Return Option from methods that might not have a result
public Option<Product> FindProduct(string sku) { // Instead of returning null or throwing return products.FirstOrDefault(p => p.Sku == sku)?.ToOption() ?? Option.None; }
Comparison with Nullable
Feature | Option<T> | Nullable (T?) |
---|---|---|
Explicit intent | ✅ Clear optional semantics | ❌ Can mean null or uninitialized |
Method chaining | ✅ Map, Filter, AndThen | ❌ Limited to ?. and ?? |
Pattern matching | ✅ Full support with Match | ⚠️ Basic null checks |
LINQ integration | ✅ IEnumerable implementation | ❌ Not enumerable |
Implicit conversion | ✅ From T to Option<T> | ⚠️ Only for value types |
Performance
- Struct-based implementation (no heap allocation)
- Aggressive inlining for common operations
- Zero-cost abstraction for most scenarios
- Suitable for high-performance code
Common Method Reference
Method | Description | Example |
---|---|---|
Some(value) |
Create Some variant | Option.Some(42) |
None |
Get None variant | Option.None |
Map(func) |
Transform value if Some | opt.Map(x => x * 2) |
Filter(predicate) |
Keep if predicate true | opt.Filter(x => x > 0) |
AndThen(func) |
Chain Option-returning operations | opt.AndThen(GetRelated) |
Or(other) |
Return this or other if None | opt.Or(fallback) |
Match(some, none) |
Pattern match on state | opt.Match(v => v, () => 0) |
Unwrap() |
Extract value (throws if None) | opt.Unwrap() |
UnwrapOr(default) |
Extract value or default | opt.UnwrapOr(0) |
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.