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.

Changing the Result signature from Result<Unit, string> to Result<Unit, Error>

See original GitHub issue

I want to gather feedback on the change I’ve been pondering lately.

The problem

In most of my projects, I use a custom Error class (with a code and a message) to denote application errors. The problem with this approach is that it requires the use of Result<T, E> or UnitResult<E>, which looks clunky and bloated.

I would like to use the simple Result<T> and Result in such situations. These classes are currently a shortcut for Result<T, string> and Result<Unit, string>, meaning that the error type is string in both of them (Unit means “nothing” or “void”).

The solution

I propose to:

  1. Introduce a new class Error with string Code and string Message fields
  2. Change Result<T> and Result such that they use the Error class as the error type instead of string

This will allow to reduce the Result<T, E> signature to just Result<T> and UnitResult<E> to just Result.

Potential issues

  1. This is a breaking change, though the consequences can be mitigated by introducing additional conversions between the string and Error classes.
  2. I don’t know how many people use custom error types vs simple strings, this change might be disruptive to all of them
  3. I don’t know whether the signature of Error is sufficient for everyone (i.e the two string fields: Code and Message). People might need additional fields and thus won’t be able to use the built-in Error and therefore will have to use the custom Result<T, E> anyway

Personally, I feel that Error should have been introduced in the library from the very beginning. String is not a good type to represent errors anyway.

Please leave your thoughts in the comments.

cc @space-alien @hankovich @linkdotnet @JvSSD

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:24
  • Comments:45 (22 by maintainers)

github_iconTop GitHub Comments

5reactions
hankovichcommented, Sep 1, 2021

Personally, I never use Result, so I have nothing to say here.

I use Result<T> quite rarely (as a return type for methods which parse value objects, for example), for me it serves just as a replacement for Result<T, string> (for cases when I do not distinguish between different error types). But it seems to me that for most projects, plain Result<T> is enough (in many places, in the case of web applications, developers just want to return 400 Bad Request + some kind of error message). Again, the entire README focuses on Result<T> only, so it seems to me that changing the error type for Result<T> would break a lot of code.

Most projects don’t need anything other than Result<T>. They are not going to do any compensation logic in case of errors (which they might need a strongly typed error representation for).

Therefore, it seems to me that such a breaking change will not benefit the library. It seems like the ideal solution to your problem would be to implement this proposal (https://github.com/dotnet/csharplang/issues/1239) so that you can write global using MyResult<T> = Result<T, MyError>. Unfortunately, we won’t see such a feature in C# 10.

I would like to add that the proposed error format is unlikely to satisfy everyone. If, nevertheless, there is a desire to make this breaking change, then I could suggest such a format of the Error type:

public class Error // not sealed to allow consumers to create their own Errors
{
    public string Message { get; }

    public IDictionary <string, object> Metadata { get; } // Possible names: [Properties, Extensions].
    // It's mutable to allow different handlers to add their own properties during processing / error handling

    // I don't really thing Code should be here, it doent's sound like it's generic enought

    public Error? InnerError { get; } // Can be handy, but I'm not 100% it's required
}

By the way, I realized that we can take a look at the ProblemDetails structure (https://datatracker.ietf.org/doc/html/rfc7807), it was also designed as an error format suitable for everyone (however, we need to exclude everything that is specific to HTTP).

Personally, I now use Result<T, TError> 99% of the time, where TError is a very tricky self-written discriminated union that covers all possible outcomes (enum is not suitable, since you cannot add additional state to enum, and for some errors it is necessary to pass additional information). And TError is different for each operation.

P.S. If I were asked to design a library from scratch, then I would probably implement Result<T> as Result<T, Error>, and not as Result<T, string>, so my single concern is compatibility.

4reactions
hankovichcommented, Jul 27, 2023

Exceptions are not errors. Exceptions are not expected (like OutOfMemoryException), while errors are expected and documented (like validation errors). I lot of exception properties only make sense after exception was thrown. What’s the benefit of retuning exceptions? I think it’s even more counter-intuitive than returning results

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why would the signature be ... -> Result<char*string>?
You can write Success () and you'll get a Result<unit> type. ... and the function can return a failure if the input parameter...
Read more >
Functional C#: Handling failures, input errors
I have a question regarding you Result / Result<t> classes to handle known exceptions. As I understand you specify simple strings or enum...
Read more >
Typesafe Error Handling in Kotlin
A common way to deal with error handling, without the use of exceptions, is with a Result<T, E> type. · The signature of...
Read more >
RESTful Post and Put Web Services User Guide
Signature is made up of the following pieces of information (concatenated together as a single string value): i. User Login Name ii. Timestamp...
Read more >
Functional error handling in F# by example - Leif Battermann
So the return type that we want to achieve is Result<unit, DomainMessage> . Let's try to map the handle function over the list...
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