Skip to content

Instantly share code, notes, and snippets.

@Lachee
Last active July 22, 2024 05:54
Show Gist options
  • Save Lachee/df07eede31e62bd129b93e2f9ec408ca to your computer and use it in GitHub Desktop.
Save Lachee/df07eede31e62bd129b93e2f9ec408ca to your computer and use it in GitHub Desktop.
Proof of concept for returning errors instead of throwing.
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); }
}
}
}
@Lachee
Copy link
Author

Lachee commented Jul 22, 2024

var result = Try.Invoke(() => MyThing());
if (result.HasException) Debug.LogException(result.Exception);

As a working prototype. Could probably use Touples instead, so you can manually check:

var ( result, error ) = Try.Invoke(() => MyThing());
if (error != null) Debug.LogException(error);

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:

var result = Try.Invoke(() => MyThing());
if (!result) Debug.LogException(result);
int someNumber = result;  // 👈 ironically this will throw if the result has an exception. Like a fused bomb 💣 😕

@Lachee
Copy link
Author

Lachee commented Jul 22, 2024

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