Packages: Specification & Body
Organize code into packages with a public specification (.ads) and hidden body (.adb), use with clauses and expanded names, keep package state, write package initialization parts, declare exceptions and constants in packages, and understand separate compilation and GNAT file naming.
The package is Ada's fundamental unit of modularity. A package specification states what a module offers — types, constants, subprograms, exceptions — and the package body contains how it is implemented. Clients can depend only on the specification, which the compiler checks separately. This physical separation of interface and implementation is enforced by the language, not by convention.
Specification and Body
A specification 'package Counters is ... end Counters;' lists the declarations clients may use. The body 'package body Counters is ... end Counters;' must implement every subprogram declared in the specification and may add its own private declarations — variables, helper subprograms, types — that no client can see. A package containing only types, constants and expression functions needs no body at all (Geometry in the example). With GNAT, the specification of package Counters lives in counters.ads and its body in counters.adb; child package A.B uses a-b.ads / a-b.adb.
• Spec (.ads) = interface; body (.adb) = implementation. • Body declarations are invisible to clients. • A body is required only if the spec needs one (subprograms, etc.). • GNAT file names: lower case, dots become dashes (shapes-text.ads).
Using a Package: with and Expanded Names
'with Counters;' at the top of a compilation unit creates a dependency and makes the package name visible. Its contents are then accessed with expanded (dotted) names: Counters.Increment, Geometry.Point. A 'use' clause (next lessons) can make them directly visible, but many Ada style guides prefer the explicit prefix because it shows where each name comes from. Because clients depend only on the spec, changing a package body requires recompiling only that body — clients need only relinking.
• with Pkg; — dependency on the spec. • Pkg.Name — expanded names make origins explicit. • Body changes don't require recompiling clients.
Package State and Initialization
Variables declared in a package body persist for the whole program run — a package with state behaves like a singleton object (Counters.Count). A body may end with 'begin ... end' statements that run once during elaboration, before the main subprogram begins; that is why "[Counters elaborated]" is printed before "Main program starts". Packages can also declare exceptions (Counters.Overflow) that clients handle by expanded name. For multiple independent counters you would instead declare a type (next lesson: private types).
• Body variables = persistent, hidden state (singleton). • begin ... in a body = initialization run at elaboration, before main. • Declare the package's exceptions in its spec.
Code Examples
Three compilation units plus the main procedure. The main program never sees Count; it can only use the operations. The fourth Increment in the final loop finds Count = Max_Count and raises Counters.Overflow. Output: [Counters elaborated] Main program starts Counter value: 2 After Reset: 0 Distance squared: 2.50000E+01 Pi * 2 = 6.28318E+00 Counters.Overflow raised at value 3 gnatmake finds the dependencies from the with clauses and the file-naming convention; you only name the main unit.
-- counters.ads package Counters is Count: Natural:= 0; -- public variable -- counters.adb package body Counters is Count: Natural:= 0; -- hidden state... -- spec exports: function Value return Natural;
Variables in the spec can be modified by any client; hide state in the body and expose operations.
-- file CountersPkg.ads containing package Counters -- file counters.ads
GNAT expects file names derived from unit names (or a project Naming package).
procedure Increment; -- in spec, no body provided -- provide package body Counters with procedure Increment is... end Increment;
Every subprogram declared in a spec must be completed in the body (or be null/abstract/expression).
Where do hidden implementation variables of a package go? A. The spec B. The body C. The main program D. A header file B. The package body.
When does a package body's 'begin ... end' part run? A. Each time a subprogram is called B. Once, during elaboration before the main program C. At program exit D. Never
B. Once at elaboration (verified: printed before 'Main program starts').
What is GNAT's file name for the spec of package Shapes.Text? A. Shapes.Text.ads B. shapes-text.ads C. shapes_text.ads D. text.ads
If only a package body changes, what must be recompiled? A. All clients B. Only the body (then relink) C. The whole program D. Only the spec
B. Only the body; clients depend on the spec.
Trace the program, including elaboration output.
Real output: [Counters elaborated] Main program starts Counter value: 2 After Reset: 0 Distance squared: 2.50000E+01 Pi * 2 = 6.28318E+00 Counters.Overflow raised at value 3
-- file: counters.ads
-- Package specification: the public interface
package Counters is
Max_Count : constant := 3; -- a named number visible to clients
procedure Increment;
procedure Reset;
function Value return Natural;
Overflow : exception; -- exceptions can be declared in packages
end Counters;
-- file: geometry.ads
-- A package with no state needs no body (only declarations)
package Geometry is
Pi : constant := 3.141_59;
type Point is record
X, Y : Float := 0.0;
end record;
function Distance_Squared (A, B : Point) return Float is
((A.X - B.X) ** 2 + (A.Y - B.Y) ** 2);
end Geometry;
-- file: counters.adb
with Ada.Text_IO;
-- Package body: the hidden implementation
package body Counters is
Count : Natural := 0; -- package state, invisible to clients
procedure Increment is
begin
if Count = Max_Count then
raise Overflow;
end if;
Count := Count + 1;
end Increment;
procedure Reset is
begin
Count := 0;
end Reset;
function Value return Natural is (Count);
begin
-- Optional initialization part: runs once, before the main program
Ad
…gnatmake -gnat2022 l25_packages.adb # compiles counters.adb, geometry.ads (checked), binds, links