In the previous section we discussed the rules in Ada which ensure that Program_Error is raised if an incorrect elaboration order is chosen. This prevents erroneous executions, but we need mechanisms to specify a correct execution and avoid the exception altogether. To achieve this, Ada provides a number of features for controlling the order of elaboration. We discuss these features in this section.
First, there are several ways of indicating to the compiler that a given unit has no elaboration problems:
A library package that does not require a body does not permit a body (this rule was introduced in Ada 95). Thus if we have a such a package, as in:
package Definitions is generic type m is new integer; package Subp is type a is array (1 .. 10) of m; type b is array (1 .. 20) of m; end Subp; end Definitions;
A package that `with's Definitions may safely instantiate Definitions.Subp because the compiler can determine that there definitely is no package body to worry about in this case
This pragma places sufficient restrictions on a unit to guarantee that no call to any subprogram in the unit can result in an elaboration problem. This means that the compiler does not need to worry about the point of elaboration of such units, and in particular, does not need to check any calls to any subprograms in this unit.
This pragma places slightly less stringent restrictions on a unit than does pragma Pure, but these restrictions are still sufficient to ensure that there are no elaboration problems with any calls to the unit.
This pragma requires that the body of a unit be elaborated immediately after its spec. Suppose a unit A has such a pragma, and unit B does a `with' of unit A. Recall that the standard rules require the spec of unit A to be elaborated before the `with'ing unit; given the pragma in A, we also know that the body of A will be elaborated before B, so that calls to A are safe and do not need a check.
Note that, unlike pragma Pure and pragma Preelaborate, the use of Elaborate_Body does not guarantee that the program is free of elaboration problems, because it may not be possible to satisfy the requested elaboration order. Let’s go back to the example with Unit_1 and Unit_2. If a programmer marks Unit_1 as Elaborate_Body, and not Unit_2, then the order of elaboration will be:
Spec of Unit_2 Spec of Unit_1 Body of Unit_1 Body of Unit_2
Now that means that the call to Func_1 in Unit_2 need not be checked, it must be safe. But the call to Func_2 in Unit_1 may still fail if Expression_1 is equal to 1, and the programmer must still take responsibility for this not being the case.
If all units carry a pragma Elaborate_Body, then all problems are eliminated, except for calls entirely within a body, which are in any case fully under programmer control. However, using the pragma everywhere is not always possible. In particular, for our Unit_1/Unit_2 example, if we marked both of them as having pragma Elaborate_Body, then clearly there would be no possible elaboration order.
The above pragmas allow a server to guarantee safe use by clients, and clearly this is the preferable approach. Consequently a good rule is to mark units as Pure or Preelaborate if possible, and if this is not possible, mark them as Elaborate_Body if possible. As we have seen, there are situations where neither of these three pragmas can be used. So we also provide methods for clients to control the order of elaboration of the servers on which they depend:
This pragma is placed in the context clause, after a `with' clause, and it requires that the body of the named unit be elaborated before the unit in which the pragma occurs. The idea is to use this pragma if the current unit calls at elaboration time, directly or indirectly, some subprogram in the named unit.
This is a stronger version of the Elaborate pragma. Consider the following example:
Unit A |withs| unit B and calls B.Func in elab code Unit B |withs| unit C, and B.Func calls C.Func
Now if we put a pragma Elaborate (B) in unit A, this ensures that the body of B is elaborated before the call, but not the body of C, so the call to C.Func could still cause Program_Error to be raised.
The effect of a pragma Elaborate_All is stronger, it requires not only that the body of the named unit be elaborated before the unit doing the `with', but also the bodies of all units that the named unit uses, following `with' links transitively. For example, if we put a pragma Elaborate_All (B) in unit A, then it requires not only that the body of B be elaborated before A, but also the body of C, because B `with's C.
We are now in a position to give a usage rule in Ada for avoiding elaboration problems, at least if dynamic dispatching and access to subprogram values are not used. We will handle these cases separately later.
The rule is simple:
`If a unit has elaboration code that can directly or indirectly make a call to a subprogram in a |withed| unit, or instantiate a generic package in a |withed| unit, then if the |withed| unit does not have pragma ‘Pure‘ or ‘Preelaborate‘, then the client should have a pragma ‘Elaborate_All‘for the |withed| unit.*'
By following this rule a client is assured that calls can be made without risk of an exception.
For generic subprogram instantiations, the rule can be relaxed to require only a pragma Elaborate since elaborating the body of a subprogram cannot cause any transitive elaboration (we are not calling the subprogram in this case, just elaborating its declaration).
If this rule is not followed, then a program may be in one of four states:
No order of elaboration exists which follows the rules, taking into account any Elaborate, Elaborate_All, or Elaborate_Body pragmas. In this case, an Ada compiler must diagnose the situation at bind time, and refuse to build an executable program.
One or more acceptable elaboration orders exist, and all of them generate an elaboration order problem. In this case, the binder can build an executable program, but Program_Error will be raised when the program is run.
One or more acceptable elaboration orders exists, and some of them work, and some do not. The programmer has not controlled the order of elaboration, so the binder may or may not pick one of the correct orders, and the program may or may not raise an exception when it is run. This is the worst case, because it means that the program may fail when moved to another compiler, or even another version of the same compiler.
One ore more acceptable elaboration orders exist, and all of them work. In this case the program runs successfully. This state of affairs can be guaranteed by following the rule we gave above, but may be true even if the rule is not followed.
Note that one additional advantage of following our rules on the use of Elaborate and Elaborate_All is that the program continues to stay in the ideal (all orders OK) state even if maintenance changes some bodies of some units. Conversely, if a program that does not follow this rule happens to be safe at some point, this state of affairs may deteriorate silently as a result of maintenance changes.
You may have noticed that the above discussion did not mention the use of Elaborate_Body. This was a deliberate omission. If you `with' an Elaborate_Body unit, it still may be the case that code in the body makes calls to some other unit, so it is still necessary to use Elaborate_All on such units.