Description
There are situations in which one might want a non-generic Result
which only contains an error, eg. methods which would usually return void
but might also return errors. Currently the best alternative to this would be defining a global Result
alias for Result<SomeUnitType>
.
The unit problem
The problem is where this SomeUnitType
should come from. The library should purposefully not include a unit type of its own as to not contribute to "unit type pollution" among the .NET ecosystem. System.ValueTuple
is a strong potential contender for a built-in unit type as it is an uninteresting struct type with no associated data. It would absolutely be preferable to automatically include this into the library by default, though without forcing the usage of specifically System.ValueTuple
.
A source-generated solution
A possible solution which would provide a nearly built-in development experience while also providing the freedom of not being constrained to a particular unit type would be to use a source generator and accompanying analyzer(s). The source generator's API surface would be a single assembly-level attribute:
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class UnitResultAttribute<T> : Attribute;
// usage:
[assembly: UnitResult<SomeUnitType>]
An incremental source generator would react to the usage of UnitResult
and generate a global using statement for Result<SomeUnitType>
along with several utility methods. Example:
// generated
global using Result = Rascal.Result<SomeUnitType>;
global using static UnitResultPrelude;
internal static class UnitResultPrelude
{
public static Result<SomeUnitType> Empty() => new(new SomeUnitType());
public static Result<SomeUnitType> EmptyErr(Error error) => new(error);
}
If the user does not wish to include the global using static UnitResultPrelude;
, the attribute could contain an optional flag to disable generation of it.
The benefit of this solution is that the API surface is minimal but could provide an arbitrary amount of complexity. The obvious downside is the complexity of a source generator, although since an analysis project with existing analyzers already exists, it wouldn't be very difficult to add a source generator. The generator itself would also be very efficient as it would be incremental and only have to react to a single attribute on the assembly-level, with some additional validity checks.