Ada provides several idioms and pragmas to aid the programmer with specifying the desired elaboration order and avoiding ABE problems altogether.
A library package which does not require a completing body does not suffer from ABE problems.
package Pack is generic type Element is private; package Containers is type Element_Array is array (1 .. 10) of Element; end Containers; end Pack;
In the example above, package Pack
does not require a body because it
does not contain any constructs which require completion in a body. As a
result, generic Pack.Containers
can be instantiated without encountering
any ABE problems.
Pragma Pure
places sufficient restrictions on a unit to guarantee that no
scenario within the unit can result in an ABE problem.
Pragma Preelaborate
is slightly less restrictive than pragma Pure
,
but still strong enough to prevent ABE problems within a unit.
Pragma Elaborate_Body
requires that the body of a unit is elaborated
immediately after its spec. This restriction guarantees that no client
scenario can invoke a server target before the target body has been
elaborated because the spec and body are effectively “glued” together.
package Server is pragma Elaborate_Body; function Func return Integer; end Server;
package body Server is function Func return Integer is begin ... end Func; end Server;
with Server; package Client is Val : constant Integer := Server.Func; end Client;
In the example above, pragma Elaborate_Body
guarantees the following
elaboration order:
spec of Server body of Server spec of Client
because the spec of Server
must be elaborated prior to Client
by
virtue of the `with' clause, and in addition the body of Server
must be
elaborated immediately after the spec of Server
.
Removing pragma Elaborate_Body
could result in the following incorrect
elaboration order:
spec of Server spec of Client body of Server
where Client
invokes Server.Func
, but the body of Server.Func
has
not been elaborated yet.
The pragmas outlined above allow a server unit to guarantee safe elaboration
use by client units. Thus it is a good rule to mark units as Pure
or
Preelaborate
, and if this is not possible, mark them as Elaborate_Body
.
There are however situations where Pure
, Preelaborate
, and
Elaborate_Body
are not applicable. Ada provides another set of pragmas for
use by client units to help ensure the elaboration safety of server units they
depend on.
Pragma Elaborate
can be placed in the context clauses of a unit, after a
`with' clause. It guarantees that both the spec and body of its argument will
be elaborated prior to the unit with the pragma. Note that other unrelated
units may be elaborated in between the spec and the body.
package Server is function Func return Integer; end Server;
package body Server is function Func return Integer is begin ... end Func; end Server;
with Server; pragma Elaborate (Server); package Client is Val : constant Integer := Server.Func; end Client;
In the example above, pragma Elaborate
guarantees the following
elaboration order:
spec of Server body of Server spec of Client
Removing pragma Elaborate
could result in the following incorrect
elaboration order:
spec of Server spec of Client body of Server
where Client
invokes Server.Func
, but the body of Server.Func
has not been elaborated yet.
Pragma Elaborate_All
is placed in the context clauses of a unit, after
a `with' clause. It guarantees that both the spec and body of its argument
will be elaborated prior to the unit with the pragma, as well as all units
`with'ed by the spec and body of the argument, recursively. Note that other
unrelated units may be elaborated in between the spec and the body.
package Math is function Factorial (Val : Natural) return Natural; end Math;
package body Math is function Factorial (Val : Natural) return Natural is begin ...; end Factorial; end Math;
package Computer is type Operation_Kind is (None, Op_Factorial); function Compute (Val : Natural; Op : Operation_Kind) return Natural; end Computer;
with Math; package body Computer is function Compute (Val : Natural; Op : Operation_Kind) return Natural is if Op = Op_Factorial then return Math.Factorial (Val); end if; return 0; end Compute; end Computer;
with Computer; pragma Elaborate_All (Computer); package Client is Val : constant Natural := Computer.Compute (123, Computer.Op_Factorial); end Client;
In the example above, pragma Elaborate_All
can result in the following
elaboration order:
spec of Math body of Math spec of Computer body of Computer spec of Client
Note that there are several allowable suborders for the specs and bodies of
Math
and Computer
, but the point is that these specs and bodies will
be elaborated prior to Client
.
Removing pragma Elaborate_All
could result in the following incorrect
elaboration order:
spec of Math spec of Computer body of Computer spec of Client body of Math
where Client
invokes Computer.Compute
, which in turn invokes
Math.Factorial
, but the body of Math.Factorial
has not been
elaborated yet.
All pragmas shown above can be summarized by the following rule:
`If a client unit elaborates a server target directly or indirectly, then if the server unit requires a body and does not have pragma Pure, Preelaborate, or Elaborate_Body, then the client unit should have pragma Elaborate or Elaborate_All for the server unit.'
If the rule outlined above is not followed, then a program may fall in one of the following states:
In this case a compiler must diagnose the situation, and refuse to build an executable program.
In this case a compiler can build an executable program, but
Program_Error
will be raised when the program is run.
In this case the programmer has not controlled the elaboration order. As a
result, a compiler may or may not pick one of the correct orders, and the
program may or may not raise Program_Error
when it is run. This is the
worst possible state because the program may fail on another compiler, or
even another version of the same compiler.
In this case a compiler can build an executable program, and the program is run successfully. This state may be guaranteed by following the outlined rules, or may be the result of good program architecture.
Note that one additional advantage of using Elaborate
and Elaborate_All
is that the program continues to stay in the last state (one or more correct
orders exist) even if maintenance changes the bodies of targets.