Exceptions are the de facto way to handle errors in C#, but they have problems. Callers don’t know if the function they’re calling will throw an exception at all or which types of exceptions it’ll throw. Exceptions also introduce an alternative control flow that’s often hard for programmers to follow. They make our code slower too, even when never thrown! Today’s article introduces an alternative to exceptions to help solve all of these issues. Read on to learn a new way to handle errors!

Back in the (very) old days there were no exceptions at all and errors were handled in one of two primitive ways. The first is to return an error code of some sort to indicate that there was a problem:

string ValidatePassword(string password)
{
	if (string.IsNullOrEmpty(password))
	{
		return null; // <-- error code
	}
 
	if (password.All(char.IsLetter))
	{
		return null; // <-- error code
	}
 
	return password.Trim();
}

Then we hope that the caller handles the error code:

void Foo()
{
	var password = ValidatePassword("abc");
	Debug.Log("password length: " + password.Length); // <-- null exception, does not print
}

Part of the problem here is that the caller has no idea that they should expect an error code. Sure, it’s obvious in this case because we all know that a validate function can fail. It won’t be nearly as obvious in more complicated code and errors will go unhandled.

The other old approach was to use an error flag rather than an error code:

class Database
{
	enum Error { Empty, OnlyLetters }
 
	Error ValidationError { get; private set; } // <-- error flag
 
	void ValidatePassword(string password)
	{
		if (string.IsNullOrEmpty(password))
		{
			ValidationError = Error.Empty; // <-- set error flag
		}
 
		if (password.All(char.IsLetter))
		{
			ValidationError = Error.OnlyLetters; // <-- set error flag
		}
	}
}

Again we expect the caller to check this error flag, but it’s even less likely since they really have to go out of their way to explicitly check the error flag.

Exceptions are just as easy for a programmer to miss. All you have to do is not write a try-catch block and the exception will go unhandled. How do you know that you should have written a try-catch block? There’s nothing in the function signature to tell you:

string ValidatePassword(string password) // <-- doesn't say this function throws an exception
{
	if (string.IsNullOrEmpty(password))
	{
		throw new Exception("empty"); // <-- throw exception
	}
 
	if (password.All(char.IsLetter))
	{
		throw new Exception("all letters"); // <-- throw exception
	}
 
	return password.Trim();
}

The difference with exceptions is that they cause catastrophic damage to the program if they’re not handled. Usually the whole program quits or, in Unity, at least skips the rest of the code for that frame. The intention is that this will get your attention as a programmer and you’ll go solve the problem. But what if the exception is never thrown during your development process and only starts happening once your end users get ahold of the app? Suddenly there are huge problems due to uncaught exceptions and you’re writing emergency patches to the code.

The fundamental issue with all three of these approaches is that none of them clearly indicate what happens when there is an error. This presents an opportunity for improvement, which I’ll now talk about. It’s not a new idea and is actually the primary way of handling errors in languages like F# (see this excellent guide). The basic idea is to wrap both the success and failure results into a single object so the return type clearly specifies both.

To implement this we need a type that can be either one thing or another but not both. Luckily, I just wrote this article about how to add unions to C#. Here’s a generic version that works for any two types:

// http://jacksondunstan.com/articles/3349
[StructLayout(LayoutKind.Explicit)]
public struct Union<TLeft, TRight>
{
	[FieldOffset(0)] public TLeft Left;
	[FieldOffset(0)] public TRight Right;
}

We could use this with ValidatePassword to indicate both the success and the failure cases to the caller:

enum Error { Empty, OnlyLetters }
 
Union<string, Error> ValidatePassword(string password)

But how would the caller handle a Union return value? They wouldn’t know if it was the left/success or right/failure case. So we need to wrap the union in another type that specifies whether it’s a success or a failure. Let’s call that type Either:

// http://jacksondunstan.com/articles/3349
public struct Either<TLeft, TRight>
{
	private bool isLeft;
	private Union<TLeft, TRight> union;
 
	public Either(TLeft left)
	{
		isLeft = true;
		union.Right = default(TRight);
		union.Left = left;
	}
 
	public Either(TRight right)
	{
		isLeft = false;
		union.Left = default(TLeft);
		union.Right = right;
	}
 
	public TLeft Left
	{
		get
		{
			if (isLeft == false)
			{
				throw new Exception("Either doesn't hold Left");
			}
			return union.Left;
		}
		set
		{
			union.Left = value;
			isLeft = true;
		}
	}
 
	public TRight Right
	{
		get
		{
			if (isLeft)
			{
				throw new Exception("Either doesn't hold Right");
			}
			return union.Right;
		}
		set
		{
			union.Right = value;
			isLeft = false;
		}
	}
 
	public bool IsLeft
	{
		get { return isLeft; }
	}
}

Now we can rewrite ValidatePassword like so:

Either<string, Error> ValidatePassword(string password)
{
	if (string.IsNullOrEmpty(password))
	{
		return new Either<string, Error>(Error.Empty);
	}
 
	if (password.All(char.IsLetter))
	{
		return new Either<string, Error>(Error.OnlyLetters);
	}
 
	return new Either<string, Error>(password.Trim());
}

And the caller can handle it like this:

void Foo()
{
	var result = ValidatePassword("abc");
	if (result.IsLeft)
	{
		Debug.Log("your password: " + result.Left);
	}
	else
	{
		Debug.Log("invalid password: " + result.Right);
	}
}

The caller is now explicitly presented with the failure case so they know that they should handle it. If there is an error and they try to use the success value, Either falls back to throwing an exception. ValidatePassword is now hard to use wrong, but we can make it easier to use right with a little extension function:

// http://jacksondunstan.com/articles/3349
public static class EitherExtensions
{
	public static TResult Match<TLeft, TRight, TResult>(
		this Either<TLeft, TRight> either,
		Func<TLeft, TResult> leftMatcher,
		Func<TRight, TResult> rightMatcher
	)
	{
		if (either.IsLeft)
		{
			return leftMatcher(either.Left);
		}
		else
		{
			return rightMatcher(either.Right);
		}
	}
}

Now the caller looks like this:

void Foo()
{
	var result = ValidatePassword("abc");
	result.Match(
		password => Debug.Log("your password: " + password),
		error => Debug.Log("invalid password: " + error)
	);
}

Those are the basics of this technique. Using Either makes it easy for function writers to declare to function callers what kinds of errors can happen. Match makes it easy to handle both the success and failure cases. There’s no alternative control flow, so it’s easy to follow what’s happening without needing to imagine a call stack and where the throw statements and catch blocks are. And no exceptions are ever actually thrown unless you ignore IsLeft and try to access the wrong value. So there’s none of the performance overhead you get with throw and catch.

That’s it for today. In next week’s follow-up article I’ll discuss how you can go further with Either to extend the error handling process to a more general technique. In the meantime, let me know what you think of Either in the comments!

Continue to Part 2

Update: Check out this comment for another interesting approach to declarative error handling using out parameters.