Error Handling

Hush implements two error mechanisms: panics and errors.

Panic

A Panic is an irrecoverable1 error, and will occur when there is a logic issue in your code. Panics will cause the whole program to crash, and execution will be terminated.

let x = 1 / 0
std.print(x)

Running the above script, you'll get:

Panic in <stdin> (line 2, column 10): division by zero

Examples of errors that cause a panic:

  • Syntax error.
  • Integer division by zero.
  • Index out of bounds.
  • Attempt to call a value that is not a function.
  • Missing or exceeding arguments in function call.

Error

Recoverable errors may be expressed through values of the error type. This is a special type in the language, and it is the only which cannot be expressed through a literal. Values of the error type can only be created using the std.error function, which expects a string description and a arbitrary context:

function check_limit(x)
	if x > 10 then
		std.error("x cannot be bigger than 10", x)
	else
		x
	end
end

One can then check whether the function has returned an error:

let result = check_limit(11)

if std.type(result) == "error" then
	std.print("Error: ", result)
else
	std.print("Success: ", result)
end

The description and context fields can be accessed as in a dictionary, but unlike in dictionaries, those cannot be assigned to:

let error = std.error("oh no!", 42)

std.print(error.description) # oh no!

error.description = "you shouldn't do that!" # panic

Examples of errors should be recoverable:

  • File not found.
  • Insufficient permission
  • Invalid format
  • Command not found
  • Command returned non-zero exit status

Try operator

The try (?2) operator may be used to early return from a function if an error occurs. It is nothing but syntax sugar for an if expression, and therefore it may be used in any expression:

function safe_div_mod(x, y)
	if y == 0 then
		std.error("division by zero", nil)
	else
		@[ div: x / y, mod: x % y ]
	end
end

# The following are equivalent:

function foo()
	let result = safe_div_mod(5, 0)
	
	let value = if std.type(result) == "error" then
		return result
	else
		result
	end

	std.print(value)
end

function bar()
	let value = safe_div_mod(5, 0)?
	std.print(value) # this won't be executed, as `?` will trigger an early return.
end

# The `?` operator may be used in any expression:
function run()
	std.print("div: ", safe_div_mod(5, 0)?.div)
end

1 One can actually catch a panic using the std.catch function, but that should be used sparingly.

2 If you're familiar with both languages, Hush's try operator might feel like the unholy child of Rust's ? operator and Go's if err != nil { return err }.