------------------------------------------------------------------------ procedure Disk_Driver is -- In this procedure, a number of important disk parameters are -- linked. Number_Of_Sectors : constant := 4; Number_Of_Tracks : constant := 200; Number_Of_Surfaces : constant := 18; Sector_Capacity : constant := 4_096; Track_Capacity : constant := Number_Of_Sectors * Sector_Capacity; Surface_Capacity : constant := Number_Of_Tracks * Track_Capacity; Disk_Capacity : constant := Number_Of_Surfaces * Surface_Capacity; type Sector_Range is range 1 .. Number_Of_Sectors; type Track_Range is range 1 .. Number_Of_Tracks; type Surface_Range is range 1 .. Number_Of_Surfaces; type Track_Map is array (Sector_Range) of ...; type Surface_Map is array (Track_Range) of Track_Map; type Disk_Map is array (Surface_Range) of Surface_Map; begin -- Disk_Driver ... end Disk_Driver; ------------------------------------------------------------------------ |
Language Ref Manual references: 3.2 Objects and Named Numbers, 4.9 Static Expressions and Static Subtypes
...
type Vector is array (Vector_Index range <>) of Element;
type Matrix is array
(Vector_Index range <>, Vector_Index range <>) of Element;
...
---------------------------------------------------------------------
procedure Matrix_Operation (Data : in Matrix) is
Workspace : Matrix (Data'Range(1), Data'Range(2));
Temp_Vector : Vector (Data'First(1) .. 2 * Data'Last(1));
...
--------------------------------------------------------------------- |
Language Ref Manual references: 3.6 Array Types, 12.1.2 Generic Formal Types, 12.3.4 Matching Rules for Formal Array Types
-- Assumption: BCD value is less than 4 digits.
function Binary_To_BCD (Binary_Value : in Natural)
return BCD; |
The next example enforces conformance with its assumption, making the checking automatic, and the comment unnecessary:
type Binary_Values is new Natural range 0 .. 9_999;
function Binary_To_BCD (Binary_Value : in Binary_Values)
return BCD; |
The next example explicitly checks and documents its assumption:
---------------------------------------------------------------------
-- Out_Of_Range raised when BCD value exceeds 4 digits.
function Binary_To_BCD (Binary_Value : in Natural)
return BCD is
Maximum_Representable : constant Natural := 9_999;
begin -- Binary_To_BCD
if Binary_Value > Maximum_Representable then
raise Out_Of_Range;
end if;
...
end Binary_To_BCD;
--------------------------------------------------------------------- |
Language Ref Manual references: 3.3 Types and Subtypes, 11.3 Raise Statements
in out.
1..10. It also appears
that parameters passed at run-time to the Put routine in any instantiation, and
values returned by the Get routine, would be similarly constrained.
subtype Range_1_10 is Integer range 1 .. 10;
---------------------------------------------------------------------
generic
Object : in out Range_1_10;
with procedure Put (Parameter : in Range_1_10);
with function Get return Range_1_10;
package Input_Output is
...
end Input_Output;
--------------------------------------------------------------------- |
However, this is not the case. Given the following legal instantiation:
subtype Range_15_30 is Integer range 15 .. 30;
Constrained_Object : Range_15_30 := 15;
procedure Constrained_Put (Parameter : in Range_15_30);
function Constrained_Get return Range_15_30;
package Constrained_Input_Output
is new Input_Output (Object => Constrained_Object,
Put => Constrained_Put,
Get => Constrained_Get);
... |
Object, Parameter, and the return value of
Get are constrained to the range
15..30. Thus, for example, if the body of the generic package contains an
assignment statement:

Object := 1;
Constraint_Error is raised when this instantiation is executed.
Thus, even with a generic unit which has been instantiated and tested many
times, and with an instantiation which reported no errors at instantiation
time, there can be a run-time error. Since the subtype constraints of the
generic formal are ignored, the Ada Language Reference Manual (Department of
Defense 1983) suggests using the name of a base type in such places to avoid
confusion. Even so, you must be careful not to assume the freedom to use any
value of the base type because the instantiation imposes the subtype
constraints of the generic actual parameter. To be safe, always refer to
specific values of the type via symbolic expressions containing attributes
like 'First, 'Last, 'Pred, and
'Succ rather than via literal values.
The best solution is to introduce a new generic formal type parameter and use it in place of the subtype, as shown below:
------------------------------------------------------------------------ generic type Object_Range is range <>; Object : in out Object_Range; with procedure Put (Parameter : in Object_Range); with function Get return Object_Range; package Input_Output is ... end Input_Output; ------------------------------------------------------------------------ |
This is a clear statement by the developer of the generic unit that no
assumptions are made about the Objects type other than that it is an integer
type. This should reduce the likelihood of any invalid assumptions being made
in the body of the generic unit.
For generics, attributes provide the means to maintain generality. It is possible to use literal values, but literals run the risk of violating some constraint. For example, assuming an array's index starts at one may cause a problem when the generic is instantiated for a zero-based array type.
Language Ref Manual references: 4.1.4 Attributes, 4.2 Literals, 12.1.1 Generic Formal Objects, 12.3 Generic Instantiation, A Predefined Language Attributes
------------------------------------------------------------------------ generic type Item is limited private; package Input_Output is procedure Put (Value : in Integer); procedure Put (Value : in Item); end Input_Output; ------------------------------------------------------------------------ |
Integer
(or any subtype of Integer) as the actual type corresponding to generic formal
Value, then the two Put procedures have identical interfaces,
and all calls to
Put are ambiguous. Therefore, this package cannot be used with type Integer.
In such a case, it is better to give unambiguous names to all subprograms. See
Section 12.3(22) of the Ada Language Reference Manual
(Department of Defense 1983) for more information.Language Ref Manual references: 6.6 Parameter and Result Type Profile - Overloading of Subprograms, 8.7 The Context of Overload Resolution, 12.3 Generic Instantiation
Concurrent access to data structures must be carefully planned to avoid errors, especially for data structures which are not atomic (see Chapter 6 for details). If a generic unit accesses one of its generic formal parameters (reads or writes the value of a generic formal object or calls a generic formal subprogram which reads or writes data) from within a task contained in the generic unit, then there is the possibility of concurrent access for which the user may not have planned. In such a case, the user should be warned by a comment in the generic specification.
Language Ref Manual references: 7.2 Package Specifications and Declarations, 9.3 Task Execution - Task Activation, 10.1.1 Context Clauses - With Clauses, 12.1.1 Generic Formal Objects
------------------------------------------------------------------------
generic
type Number is limited private;
with procedure Get (Value : out Number);
procedure Process_Numbers;
------------------------------------------------------------------------
procedure Process_Numbers is
Local : Number;
procedure Perform_Cleanup_Necessary_For_Process_Numbers is
separate;
...
begin -- Process_Numbers
...
Catch_Exceptions_Generated_By_Get:
begin
Get(Local);
exception
when others =>
Perform_Cleanup_Necessary_For_Process_Numbers;
raise;
end Catch_Exceptions_Generated_By_Get;
...
end Process_Numbers;
------------------------------------------------------------------------ |
In particular, when an exception is raised by a generic formal subprogram, the
generic unit is in no position to understand why or to know what corrective
action to take. Therefore, such exceptions should always be propagated back to
the caller of the generic instantiation. However, the generic unit must first
clean up after itself, restoring its internal data structures to a correct
state so that future calls may be made to it after the caller has dealt with
the current exception. For this reason, all calls to generic formal
subprograms should be within the scope of a when others exception handler if
the internal state is modified, as shown in the example above.
When a reusable part is invoked, the user of the part should be able to know exactly what operation (at the appropriate level of abstraction) has been performed. For this to be possible, a reusable part must always do all or none of its specified function; it must never do half. Therefore, any reusable part which terminates early by raising or propagating an exception should return to the caller with no effect on the internal or external state. The easiest way to do this is to test for all possible exceptional conditions before making any state changes (modifying internal state variables, making calls to other reusable parts to modify their states, updating files, etc.). When this is not possible, it is best to restore all internal and external states to the values which were current when the part was invoked before raising or propagating the exception. Even when this is not possible, it is important to document this potentially hazardous situation in the comment header of the specification of the part.
A similar problem arises with parameters of mode out or in out when exceptions
are raised. The Ada language defines these modes in terms of "copy-in" and
"copy-back" semantics, but leaves the actual parameter-passing mechanism
undefined. When an exception is raised, the copy-back does not occur, but for
an Ada compiler which passes parameters by reference, the actual parameter has
already been updated. When parameters are passed by copy, the update does not
occur. To reduce ambiguity, increase portability, and avoid situations where
some but not all of the actual parameters are updated when an exception is
raised, it is best to treat values of out and in out parameters like state
variables, updating them only after it is certain that no exception will be
raised.
Language Ref Manual references: 11.2 Exception Handlers, 11.3 Raise Statements, 11.4 Exception Handling, 12.2 Generic Bodies