Error Handling Directives
Error handling Directives start with an exclamation mark (:!)
.
The error handling model in U simplifies error management without compromising performance or flexibility. The compiler ensures errors are handled where they occur, preventing unexpected runtime issues due to oversight.
Key features of U's Error Model include:
- Simplicity: It is designed to be straightforward yet powerful.
- Reliability: The Error Model and Type System form the foundation of system reliability, ensuring validity by design.
- Efficiency: It adds minimal overhead at compile or run time, optimizing performance. Success paths incur:
- For success path: almost zero overhead
- For failure paths: any additional costs on failure paths are justified by the need for fast error management
- Concurrency: Carefully crafted to avoid introducing elusive bugs in distributed and highly concurrent systems.
- Observability: Facilitates effective debugging and production diagnostics.
- Composability: Central to code expression, offering flexibility to use statuses or exceptions as needed.
The U Error Model is customizable by users and can be omitted during builds for rapid prototyping purposes.
Principles¶
According to established standards like IEEE 610.12-1990 and others, U uses the following terminology for error handling:
- Error: Refers to a mistake in the implementation, typically identified as incorrect lines of code.
- Fault (Bug): An execution event that manifests due to an error but may remain unnoticed. It represents an incorrect step or process causing unintended software behavior.
- Defect: An incorrect internal state resulting from a fault. A defect is characterized by the presence of an incorrect state.
- Failure: Refers to observed incorrect behavior where the system or components fail to perform their expected functions as intended.
These terms provide a structured framework for understanding and managing errors, faults, defects, and failures in U's error handling approach.
Here is an example of a function returning a velocity:
:velocity? : {: length , time
length / time
}
# Error
:v : velocity? 10, 0 # => Divide by Zero Error
In this example:
- Failure:
velocity?
failed to return a result - Defect: dividing 10 by 0 is not allowed
- Fault: executed 10 / 2
- Error: no check for
time
equal to 0 before dividinglength
In U's error management system, there are two types of error value categories:
-
Result: This type consists of values that are checked after a function call. A Result always includes one value marked as correct, while other cases must be handled separately. The term "Result" suggests something that follows or accompanies a result.
-
Exception: An exception value is thrown within the code and requires specific handling at a predefined location separate from where it was thrown.
These distinctions help clarify how errors and exceptions are managed within the U programming language, providing a structured approach to error handling.
Result¶
A straightforward method to manage errors is through the use of function Results. Results are highly efficient because they do not rely on runtime support. Essentially, a function either returns a value or modifies a parameter to indicate success or failure. Results often consist of sets of error codes or functions designed for error management.
# Define an Result called <login_status>
@!login_status :Ok, :Error
:login : {: name
...
# Return
\^! Ok, db.user name
}
# '.!' <=> call <login> and check Result
login.! :Alice, ({: user
# No error value <=> 'Ok'
}, {!?: status, user
# '!?:' means match status with...
# Deal with the Result as status.
:- Error, ...
})
# Or
login.! :Alice, {!?: status, user
# Deal with the status.
:- Ok, ...
:- Error, ...
}
Exceptions¶
An exception is a recoverable event that occurs during program execution. Exceptions are particularly useful for executing the same instructions from different source code locations and handling multiple error types. For example, exceptions can consistently free allocated memory when network or computation errors occur, regardless of where the error happens in the code base.
There are numerous scenarios where an error can be recovered upon occurrence:
- File and Network I/O.
- Parsing data (e.g., a compiler parser).
- Validating user data (e.g., a web form submission).
In U, exceptions when used as library, are managed carefully. Functions cannot throw exceptions unless they are explicitly annotated to do so.
login : {!> ::Exception :MyException # <login> can throws MyException
...
# Throws my exception
:!> MyException, "Exception should be catched elsewhere"
}
# Call with exception management
login.!> 'Bob', {
:!- MyException, {
...
}
}
Main differences between Exceptions and Results are:
-
Result must be managed after each call, whilst Exceptions can be managed anywhere within nested calls, for example:
In the above example, exceptions must be managed within f1 to f3. -
Results are the only one to guarantee a sequential execution. Exceptions can appear anywhere. They must be carefully used to ensure correct behaviors.
For concurrent tasks, Results are the default behaviour. Exception are available as a library.