Skip to content

Optimization of Result<T> and Errors#4

Merged
feO2x merged 20 commits intomainfrom
3-optimization-of-resultt-and-errors
Jan 14, 2026
Merged

Optimization of Result<T> and Errors#4
feO2x merged 20 commits intomainfrom
3-optimization-of-resultt-and-errors

Conversation

@feO2x
Copy link
Copy Markdown
Owner

@feO2x feO2x commented Jan 7, 2026

  • Errors now exposes SBO storage via ReadOnlyMemory<Error> and an allocation-free enumerator.
  • Result<T>.Errors replaces the old ImmutableArray copy, TryGetValue is implemented with the proper nullability attributes, and the non-generic wrapper delegates accordingly.
  • Error now carries Source, CorrelationId, and Category, with helper factories/tests validating defaults and fluent APIs.
  • MetadataObjectExtensions.MergeIfNeeded exists and is used, reducing redundant merges

@feO2x feO2x self-assigned this Jan 7, 2026
@feO2x feO2x linked an issue Jan 7, 2026 that may be closed by this pull request
feO2x added 15 commits January 7, 2026 22:02
Error is no longer a record struct, but implements IEquatable<T> directly. It offers overloads to exclude Metadata comparison. Errors does the same thing now, it supports leaving out metadata comparison.

Signed-off-by: Kenny Pflug <[email protected]>
…HasOptionalMetadata<T>

This involves Correlation ID and Source. This commit also includes a refactoring of the metadata integration in Result<T> and Result by introducing the IHasOptionalMetadata<T> interface. Metadata related methods are now mostly extension methods.

Signed-off-by: Kenny Pflug <[email protected]>
@feO2x
Copy link
Copy Markdown
Owner Author

feO2x commented Jan 14, 2026

Further implementation notes:

Tracing support via metadata extensions (architectural decision)

Original plan: Add Source (string?) and CorrelationId (Guid?) as direct fields on the Error struct.

Implemented approach: Keep Error struct minimal and provide tracing support through metadata extensions via the
IHasOptionalMetadata<T> interface.

Rationale for deviation:

  1. Smaller struct size: Keeping Error minimal improves cache locality and reduces memory footprint. Adding
    Source (8 bytes ref + 1 byte hasValue) and CorrelationId (16 bytes Guid + 1 byte hasValue) would add ~26 bytes
    per error.
  2. Flexibility: Metadata-based approach allows arbitrary tracing properties without bloating the core Error type.
    Different applications may need different tracing identifiers (trace ID, span ID, request ID, etc.).
  3. Opt-in overhead: Applications that don't use tracing don't pay for unused fields. Metadata allocation only occurs
    when explicitly needed.
  4. Extensibility without breaking changes: New tracing properties can be added via extension methods without
    modifying the Error struct signature.

Implementation details:

  • Introduced IHasOptionalMetadata<T> interface to unify metadata operations across Result<T>, Result, and
    potentially Error in the future
  • Tracing extension methods in Light.Results.MetadataExtensions namespace provide:
    • WithSource(string source) - adds source identifier to metadata
    • WithCorrelationId(string correlationId) - adds correlation ID to metadata
    • WithTracing(string source, string correlationId) - adds both in one call
    • TryGetSource(out string? source) - retrieves source from metadata
    • TryGetCorrelationId(out string? correlationId) - retrieves correlation ID from metadata
  • Uses string for correlation ID (not Guid) to support various formats (UUID, hex strings, custom formats)
  • Metadata keys: "source" and "correlationId"

Trade-offs accepted:

  • ❌ Requires metadata allocation for tracing (vs. inline fields)
  • ❌ Lookup overhead via dictionary access (vs. direct field access)
  • ✅ Significantly smaller Error struct
  • ✅ Zero cost for non-tracing scenarios
  • ✅ More flexible and extensible

Serialization implications:

  • RFC 9457/gRPC serialization adapters should check both Error.Metadata and Result.Metadata for tracing properties
  • Map metadata["source"]extensions.source or instance
  • Map metadata["correlationId"]traceId or equivalent tracing header

feO2x added 2 commits January 14, 2026 08:04
Signed-off-by: Kenny Pflug <[email protected]>
This is actually related to C# 14 Extension Blocks. I had to revert back to Extension Methods to fix this. See MetadataExtensions.cs for details.

Signed-off-by: Kenny Pflug <[email protected]>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes Result<T> and Errors for performance by implementing Small Buffer Optimization (SBO) for error storage, replacing ImmutableArray<Error> with a struct-based Errors type that exposes ReadOnlyMemory<Error>, and adding TryGetValue with proper nullability. The Error struct now includes Category, Source, CorrelationId fields (via metadata extensions), and uses required properties. The MetadataObjectExtensions.MergeIfNeeded method reduces redundant metadata merges.

Changes:

  • Replaced ImmutableArray<Error> with Errors struct using SBO and ReadOnlyMemory<Error>
  • Added Result<T>.TryGetValue and changed IsSuccess/IsFailure to IsValid
  • Enhanced Error with Category enum and validation, removed record struct pattern
  • Implemented metadata optimization via MergeIfNeeded and IHasOptionalMetadata<T> interface

Reviewed changes

Copilot reviewed 79 out of 81 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Light.Results/Error.cs Converted from record struct to regular struct with required Message property, added Category enum, implemented custom equality/hashing
src/Light.Results/ErrorCategory.cs New enum mapping HTTP status codes to error categories
src/Light.Results/Errors.cs Complete rewrite with SBO, ReadOnlyMemory storage, custom enumerator, and optimized equality checking
src/Light.Results/Result.cs Changed IsSuccess/IsFailure to IsValid, added TryGetValue, replaced ErrorList with Errors property, added equality operators
src/Light.Results/Metadata/*.cs Added IHasOptionalMetadata interface, MergeIfNeeded optimization, value-based equality for MetadataValue/Object/Array
src/Light.Results/MetadataExtensions/Tracing.cs New tracing extensions for Source and CorrelationId via metadata
tests/* Comprehensive test coverage for all new functionality
Comments suppressed due to low confidence (1)

src/Light.Results/ErrorCategory.cs:1

  • Typo in test method name: 'UnavailableForLegaReasons' should be 'UnavailableForLegalReasons' (missing 'l' in 'Legal').

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Light.Results/Unit.cs Outdated
Comment thread tests/Light.Results.Tests/GenericResultEqualityTests.cs
Comment thread tests/Light.Results.Tests/NonGenericResultEqualityTests.cs
Comment thread tests/Light.Results.Tests/ErrorsAdditionalTests.cs
Comment thread src/Light.Results/Result.cs
Comment thread benchmarks/Benchmarks/ResultBenchmarks.cs
Comment thread benchmarks/Benchmarks/ResultBenchmarks.cs
@feO2x feO2x merged commit 5ca5785 into main Jan 14, 2026
1 check passed
@feO2x feO2x deleted the 3-optimization-of-resultt-and-errors branch January 14, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimization of Result<T> and Errors

2 participants