Errors and other exceptional situations must be handled by programs that work in the real world. Ada provides facilities to deal with these real problems which make handling them much easier. In Ada, an exception represents a kind of exceptional situation, usually a serious error. At run-time an exception can be raised, which calls attention to the fact that an exceptional situation has occurred.

The default action when an exception is raised is to halt the program. Usually the program will print out the name of the exception and where the problem took place, though this depends on your compiler. The next few sections will show how to override this default.

If you don't want to halt the program, you'll need to tell Ada what to do instead by defining an exception handler. An exception handler states what exceptions to handle and what to do when a given exception is raised.

Exceptions generally represent something unusual and not normally expected - reserve their use for things like serious error conditions. They shouldn't be used for ``expected'' situations, because they can be slower and if incorrectly handled can stop a program. The place where an exception is raised may be far away from where it is handled, and that makes programs with a voluminous number of different exceptions harder to understand. Instead, exceptions should be used when a subprogram cannot perform its job for some significant reason.

Ada has a number of predefined exceptions that are raised when certain language-defined checks fail. The predefined check you're most likely to see is Constraint_Error; this exception is raised when a value goes out-of-bounds for its type. Examples of this include trying to store a value that's too large or too small into that type, dividing by zero, or using an invalid array index.

Naturally, there is some run-time overhead in performing all these checks, though less than you might think. It is possible to suppress these language-defined checks; this should only be done after the program is thoroughly debugged, and many people think that it shouldn't be done even then.

Some packages define their own exceptions, for example, Text_IO defines the exception End_Error that is raised when you attempt to ``Get'' something after you've reached the end of the file, and Name_Error is raised if try to open a file that doesn't exist.

In the next few sections we'll learn how to define exceptions, how to raise exceptions, and how to handle exceptions. If you're defining a package that displays the view from an airplane cockpit, should you raise an exception whenever the view changes from daytime to nighttime? You should probably raise an exception. You should probably not raise an exception. Sorry, that's probably not a good approach. You should only use exceptions when serious problems arise, not just when some interesting state changes. It might be a good approach, but it probably isn't.

Before you can raise or handle an exception, it must be declared. Declaring exceptions is just like declaring a variable of type exception; here's an example:

  Singularity : exception;

To be complete, here's the syntax for defining an exception, describing using BNF:

  exception_declaration ::= defining_identifier_list ": exception;"
  defining_identifier_list ::= identifier { "," identifier }

Exception declarations are generally placed in a package declaration.

Raising an exception is easy, too - just use the raise statement. A raise statement is simply the keyword "raise" followed by the name of the exception. For example, to raise the "Singularity" exception defined above, just say:

  raise Singularity;

The syntax in BNF is:

  raise_statement ::= "raise" [ exception_name ] ";"

You'll notice that the exception_name is optional; we'll discuss what that means in the next section. Which of the following would define an exception named No_Safety_Net? No_Safety_Net : exception; Exception : No_Safety_Net; That's right. No, you've got it reversed.

As we've noted many times, if an exception is raised and isn't handled the program stops. To handle exceptions you must define, reasonably enough, an exception handler.

When an exception is raised (by the raise statement) Ada will abandon what it was doing and look for a matching exception handler in the sequence of statements where the exception was raised. A sequence of statements is the set of statements between the keywords "begin" and "end". If Ada doesn't find a match, it returns from the current subprogram (cleaning up along the way) and tries to find a matching exception handler in the caller (from where it was called). If it doesn't find one there, it exits that subprogram (cleaning up along the way) and tries yet again. Ada keeps repeating this process until it finds a matching exception handler or until it exits the program.

An exception handler is defined just before the "end" statement that matches a "begin" statement.

For example, here's a procedure that "Open"s a file if it can, and if the file doesn't exist it "Create"s it.

  procedure Open_Or_Create(File : in out File_Type;
                           Mode : File_Mode; Name : String) is
  begin
    -- Try to open the file. This will raise Name_Error if
    -- it doesn't exist.
    Open(File, Mode, Name);
  exception
    when Name_Error =>
      Create(File, Mode, Name);
  end Open_Or_Create;

Here's a simplified BNF of an exception handler:

  exception_handler ::= exception_choice { "|"  exception_choice } "=>"
                        sequence_of_statements
  exception_choice  ::= exception_name | "others"

The keyword "others" means all exceptions not explicitly listed in this exception handler; thus, you can handle all exceptions if you want to.

Inside an exception handler you can do any kind of processing you wish. If, after processing, you find you need to raise the same exception to a higher level, you can use the "raise" statement without the name of an exception. A raise statement without a named exception re-raises the exception being handled. Raise statements can only re-raise exceptions inside an exception handler.

You can pass information along with an exception, and there is a predefined package of exception-related operations. We won't go into that now, but if you're interested, you can examine section 11 of the Ada RM, which discusses exceptions.