Reflect

Error Handling

Error handling is making use of the enum feature of the language to embed error information into the return value of a function. Error codes can either be checked and handled manually, or they can be handled indirectly using an implicit or explicit try/catch statement. As such, they provide much of the same syntactic leanness of true exceptions, while also providing the portability and performance of using simple error codes.

Example

enum DecodeError {
	unternimatedSequence,
	invalidSequence,
	maximumLengthExceeded
}

@error(DecodeError)
function decode(text)
{
	var ret: string;
	for (i in 0 .. text.length) {
		if (text[i] == '\\') {
			if (i + 1 < text.length)
				throw DecodeError.unterminatedEscapeSequence;
			ret ~= decodeSequence(text[i+1]);
			i++;
		} else ret ~= text[i];
	}
	return ret;
}

function main()
{
	let str = "Hello, World\\";

	// manually check for errors
	let res = decode(str);
	assert(!res.success)
	assert(res.error == DecodeError.unterminatedSequence);

	// use exception style error handling - note the "!" operator
	try {
		print("Decoded: ", decode(str)!);
	} catch (error: DecodeError) {
		print("Error decoding string: ", error);
	}
}

Handling errors

Function Error Annotations

Any function that might either directly, using throw, or indirectly, using the ! operator, throw an error must be annotated with an @error annotation. The enum type passed to the annotation must be a superset of all error types that might be thrown.

Even if errors may be generated inside, functions do not need to specify an @error annotation if they handle all errors by checking return values, or enclose all possibly throwing code with try {} catch {} clauses that handle all possible errors.

Combining Multiple Error Enums

The | operator can be used to combine two or more error enums into a combined error type that can hold any value that is representable by either of those enums.

Example

@error(IOError | DecodeError)
function loadEncodedFile(path)
{
	var encoded = readFileUTF8(path)!;
	return decode(encoded)!;
}

function main()
{
	// Catch by error-sub type:
	try
		print(loadEncodedFile("text.txt"));
	catch (error: IOError)
		print("The file could not be read: ", error);
	catch (error: DecodeError)
		print("The encoding of the file is incorrect: ", error);

	// Catch by combined type:
	try
		print(loadEncodedFile("text.txt"));
	catch (error: (IOError | DecodeError))
		print("Error reading encoded file: ", error);
}

Return Value of @error Functions

The return value of any function that has an @error annotation will be augmented by the compiler with the optional error value. This is done by mapping the plain return type T to the type core.error.ErrorReturn(T, ErrorType), where ErrorType is the type passed to the @error annotation.

ErrorReturn supports the following primitives for working with error results:

The Implicit Try-Catch

Any function with an @error annotation will be wrapped inside of a compiler-generated try/catch statement that catches any uncaught error and returns it as part of the function's ErrorReturn return value.