question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Proposal: Result struct providing generic error only

See original GitHub issue

Problem: For some operations, we don’t need to know what the resulting value was, only the execution status (explicitly) and failure errors. There are no structs in functional Result mechanics designed specifically for such scenarios. Limitations of existing solutions: We can pass generic errors with struct Result<T, E>. But it has some problems:

  1. We must pass value even if we dont want to

  2. If we create and use some kind of void or unit type as value, we still must specify both types explicitly:

    Result.Ok<UnitTypeStub, ErrorClass>(UnitTypeStub.Default)
    Result.Fail<UnitTypeStub, ErrorClass>(new ErrorClass())
    

    while we want

    Result.Fail(new ErrorClass())
    Result.Ok<ErrorClass>()
    
    

References: The same idea was raised at the end of the discussion within issue #35. According to #124 and #122 having Result struct with generic error and no value would help people to code their intentions and expectations more clearly.

Proposed solution: We need additional type to existing Result, Result<T>, Result<T, E>. Unfortunately, we cannot implement this struct as a generic Result<E> literally, but we can make semantic deviation of a new struct as small as possible.

Struct draft I came up with
[Serializable]
public struct UnitResult<E> : IResult, ISerializable
{
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly ResultCommonLogic<E> _logic;

    public bool IsFailure => _logic.IsFailure;
    public bool IsSuccess => _logic.IsSuccess;
    public E Error => _logic.Error;

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        _logic.GetObjectData(info, context);
    }

    [DebuggerStepThrough]
    internal UnitResult(bool isFailure, E error)
    {
        _logic = new ResultCommonLogic<E>(isFailure, error);
    }

    UnitResult(SerializationInfo info, StreamingContext context)
    {
        bool isFailure = info.GetBoolean("IsFailure");

        E error = isFailure ? (E)info.GetValue("Error", typeof(E)) : default;
        _logic = new ResultCommonLogic<E>(isFailure, error);
    }

    public static implicit operator Result(UnitResult<E> result)
    {
        if (result.IsSuccess)
            return Result.Ok();
        else
            return Result.Fail(result.Error.ToString());
    }

    public void Deconstruct(out bool isSuccess, out bool isFailure)
    {
        isSuccess = IsSuccess;
        isFailure = IsFailure;
    }

    public void Deconstruct(out bool isSuccess, out bool isFailure, out E error)
    {
        isSuccess = IsSuccess;
        isFailure = IsFailure;
        error = IsFailure ? Error : default;
    }
}

public static class UnitResult
{
    [DebuggerStepThrough]
    public static UnitResult<E> Ok<E>()
    {
        return new UnitResult<E>(false, default);
    }

    [DebuggerStepThrough]
    public static UnitResult<E> Fail<E>(E error)
    {
        return new UnitResult<E>(true, error);
    }

    public static UnitResult<E> Create<E>(bool isSuccess, E error)
    {
        return isSuccess
            ? Ok<E>()
            : Fail(error);
    }

    public static UnitResult<E> Create<E>(Func<bool> predicate, E error)
    {
        return Create(predicate(), error);
    }

    public static async Task<UnitResult<E>> Create<E>(Func<Task<bool>> predicate, E error)
    {
        bool isSuccess = await predicate().ConfigureAwait(Result.DefaultConfigureAwait);
        return Create(isSuccess, error);
    }
}

What do you think? Does it worth the implementation? Does it fit library’s direction?

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:7
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

5reactions
vkhorikovcommented, Jun 3, 2019

Apology for the late answer. I personally don’t have any objections, looks like a good feature well worth adding to the library.

1reaction
vkhorikovcommented, Feb 1, 2020

Unfortunately, no

Read more comments on GitHub >

github_iconTop Results From Across the Web

Proposal to make Result a class instead of a struct #151
Issue Proposal: Result struct providing generic error only #125 highlights the requirement for a non-value Result with a custom error type, ...
Read more >
Swift: How to cast error to generic struct
I am being passed an Error at runtime. This error is actually a generic struct. I need to cast the error to this...
Read more >
How to define a generic Result type that either contains a ...
Hi there, I'm new to Go and would like to know how to define a basic Result type that either comes with a...
Read more >
How to Add Extra Information to Errors in Go
In this tutorial, you'll create a program that uses these functions to include additional information in errors returned from your functions, ...
Read more >
How to fix the error “protocol can only be used as a generic ...
How to fix the error “protocol can only be used as a generic constraint because it has Self or associated type requirements”.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found