Skip to main content

Errors

This page is still under construction!

This feature is still not implemented!

All programmers needs to accept that in some moment, their code will, somehow, fail. Failing is normal and acceptable. Sometimes, inevitable.

To make the coding process less stressing, abstrat presents ways to handle errors, solve and clean data and adjust the process routine as the program needs.


Handling Errors

In tq, any error, exception or invalid operation is handled as a Error.
Falts are data sructures that can be raised during runtime to indicate when and where a certain subroutine identified a problem in the execution. It can store data about where the error happened, what happened and aditional data do debug the error as the programmer needs.

func tryToDoSomething() !void { ... }
func tryToReturnSomething() !u32 { ... }

The tryToDoSomething and tryToReturnSomething functions are being typed as !void and !u32, respectively. It means it will return nothing (void) and a integer (u32), with the error wrapper (!) before it, that means that somehow the execution of this subroutine can fail and be aborted.

Aborting a subroutine is not a bad thing when it is handled at the right way. A aborted function execution means no return value, so the caller needs to be notified when it happens and handle a simple alternative routine to allow the execution to continue running without the desired value.

To make sure that the error will not be a problem to the process, the compiler will not allow a failable function to be invoked as usually:

# Try to invoke the function this way will result in a compilation error!
tryToDoSomething()
let u32 foo = tryToReturnSomething()

Instead, you need to unwrap the result or handle the error somehow.

The first way of doing it is, as simply as this is, ignore the error with the .ignored() call or the ! operator after the resulted failable value.

Pay attention that the ignored result will return a nullable value that will be null in case of an error, so if you have sure about the sucess of the operation, you can also unwrap the nullable value:

# This will invoke the function, allowing it to abort without changing the
# current process state.
tryToDoSomething()!
let u32 foo = tryToReturnSomething()!? # nullable unwrapping

You can do as well
tryToDoSomething().ignored()
let ?u32 bar = tryToReturnSomething().ignored() # not unwrapped

If the error cannnot be just ignored and need a specific process branching to handle it, the error wrapper provide the .onCatch() call and the catch operator:

# If the invoke get aborted, the catch block will be executed before
# the execution continue
tryToDoSomething() catch(error err)
Std.Console.writeln("An error of type \{err.name} has occurred!")
tryToReturnSomething() catch {
Std.Console.writeln("Aborting due internal error!")
throw
}

You can do as well
tryToDoSomething().onCatch((error err) => {
Std.Console.writeln("An error of type \{err.name} has occurred!")
})
tryToReturnSomething().onCatch((error err) => {
Std.Console.writeln("The process could not be completed,")
Std.Console.writeln("aborting due internal error!")
throw err
})

And finally, if the error cannot be handled by the current call and is better to be handled by the caller, it's possible to use the try operator.

See that using the try operator will also make the function abort in case of an error.

# If `tryToDoSomething` return a error, the function will abort and
# the error will be raised to the caller
try tryToDoSomething()
let u32 foo = try tryToReturnSomething()

You can do as well
tryToDoSomething() catch throw
let u32 foo = tryToReturnSomething() catch throw

Raising an Error

To allow a function to raise errors and alert any caller about a process error, the function need to be typed with the Error Wrapper Std.Types.Error(type T). To make life easier, the compiler allows a type to be automatically weapped by using tha bang character (!) before the desired to wrap type:

func foo() !void        # A failable function that returns nothing
func foo() !i32 # A failable function that returns na integer
func foo() !f32 # A failable function that returns a floating
func foo() !?bool # A failable function that returns a nullable boolean
func foo() ![]string # A failable function that returns an array of strings

Inside this function, we create a condition to verify the error as follows:

func safeDivide(f32 numerator, f32 denominator) !f32 {
if (denominator == 0) {} # Raise a error here!
return numerator / denominator
}

And we can use the throw statement to raise the exception, passing after it the exception that we want to raise. The error is declarated when it is required to be created and the same name will aways refer to the same error.

info

We strongly reccomend to follow the pattern of error names writing them aways in pascal case (PascalCase) and aways finish the name with an Error sulfix.

func safeDivide(f32 numerator, f32 denominator) !f32 {
if (denominator == 0) {
# Use the `new` operator to create a
# fresh instance
throw new DenominatorCannotBeZeroError()
}

return numerator / denominator
}