Last active
July 22, 2024 05:54
-
-
Save Lachee/df07eede31e62bd129b93e2f9ec408ca to your computer and use it in GitHub Desktop.
Proof of concept for returning errors instead of throwing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.CompilerServices; | |
using UnityEngine.Events; | |
namespace StopMission.Utilities | |
{ | |
public readonly struct Result<TException> | |
where TException : Exception | |
{ | |
public readonly TException Exception; | |
public readonly bool HasException; | |
public Result(TException error) | |
{ | |
this.Exception = error; | |
this.HasException = true; | |
} | |
public static Result<Exception> From() | |
{ | |
return new Result<Exception>(); | |
} | |
public static implicit operator bool(Result<TException> result) | |
{ | |
return !result.HasException; | |
} | |
public static implicit operator TException(Result<TException> result) | |
{ | |
if (!result.HasException) | |
return null; | |
return result.Exception; | |
} | |
} | |
public readonly struct Result<TValue, TExeption> | |
where TExeption : Exception | |
{ | |
public readonly TValue Value; | |
public readonly bool HasValue; | |
public readonly TExeption Exception; | |
public readonly bool HasException; | |
public Result(TValue value) | |
{ | |
this.Value = value; | |
this.Exception = null; | |
this.HasValue = true; | |
this.HasException = false; | |
} | |
public Result(TExeption error) | |
{ | |
this.Value = default; | |
this.Exception = error; | |
this.HasValue = false; | |
this.HasException = true; | |
} | |
public static Result<TValue, Exception> From(TValue result) | |
{ | |
return new Result<TValue, Exception>(result); | |
} | |
public static implicit operator bool(Result<TValue, TExeption> result) | |
{ | |
return !result.HasException; | |
} | |
public static implicit operator TExeption(Result<TValue, TExeption> result) | |
{ | |
if (!result.HasException) | |
return null; | |
return result.Exception; | |
} | |
public static implicit operator TValue(Result<TValue, TExeption> result) | |
{ | |
if (!result.HasValue) | |
throw new AggregateException("Result has an error instead of a value.", result.Exception); | |
return result.Value; | |
} | |
} | |
public static class Try | |
{ | |
#region Result<Exception> | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Result<Exception> Invoke(Action func) | |
{ | |
return Invoke<Exception>(func); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Result<TError> Invoke<TError>(Action func) | |
where TError : Exception | |
{ | |
try | |
{ | |
func(); | |
return new Result<TError>(); | |
} | |
catch (TError error) | |
{ | |
return new Result<TError>(error); | |
} | |
} | |
#endregion | |
#region Result<Value, Exception> | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Result<TValue, Exception> Invoke<TValue>(Func<TValue> func) | |
{ | |
return Invoke<TValue, Exception>(func); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Result<TValue, TError> Invoke<TValue, TError>(Func<TValue> func) | |
where TError : Exception | |
{ | |
try | |
{ | |
TValue value = func(); | |
return new Result<TValue, TError>(value); | |
} | |
catch (TError error) | |
{ | |
return new Result<TValue, TError>(error); | |
} | |
} | |
#endregion | |
} | |
public static class TryExtensions | |
{ | |
public static Result<Exception> TryInvoke(this UnityEvent evt) | |
{ | |
try | |
{ | |
evt.Invoke(); | |
return new Result<Exception>(); | |
} | |
catch (Exception e) { return new Result<Exception>(e); } | |
} | |
public static Result<Exception> TryInvoke<T0>(this UnityEvent<T0> evt, T0 arg0) | |
{ | |
try | |
{ | |
evt.Invoke(arg0); | |
return new Result<Exception>(); | |
} | |
catch (Exception e) { return new Result<Exception>(e); } | |
} | |
public static Result<Exception> TryInvoke<T0, T1>(this UnityEvent<T0, T1> evt, T0 arg0, T1 arg1) | |
{ | |
try | |
{ | |
evt.Invoke(arg0, arg1); | |
return new Result<Exception>(); | |
} | |
catch (Exception e) { return new Result<Exception>(e); } | |
} | |
public static Result<Exception> TryInvoke<T0, T1, T2>(this UnityEvent<T0, T1, T2> evt, T0 arg0, T1 arg1, T2 arg2) | |
{ | |
try | |
{ | |
evt.Invoke(arg0, arg1, arg2); | |
return new Result<Exception>(); | |
} | |
catch (Exception e) { return new Result<Exception>(e); } | |
} | |
public static Result<Exception> TryInvoke<T0, T1, T2, T3>(this UnityEvent<T0, T1, T2, T3> evt, T0 arg0, T1 arg1, T2 arg2, T3 arg3) | |
{ | |
try | |
{ | |
evt.Invoke(arg0, arg1, arg2, arg3); | |
return new Result<Exception>(); | |
} | |
catch (Exception e) { return new Result<Exception>(e); } | |
} | |
} | |
} |
the implicit casts might not be to everyones tastes. The idea of it throwing later is really awkward and a annoying work around to ensure safety. Probably better to actually remove them and make it all explicit like C#'s Nullable.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
As a working prototype. Could probably use Touples instead, so you can manually check:
Depends if you prefer the OOP style or more like how Go handles things. I personally dont like having to check
!= null
and prefer implicit casts, as that is how Unity handles objects too. Consistency is very nice.Im unsure the performance penalty of touples, but surely it cant be worse than the struct im using.
For maximum lazy, this also has implicits: