Private Types & Information Hiding
Declare private types whose representation is hidden in the private part, write deferred constants, understand the partial vs full view, what operations clients keep (assignment, equality), and design abstract data types enforced by the compiler.
A private type lets clients use a type by name and through the operations the package provides, while its representation is hidden in the package's private part. The compiler enforces the boundary: code outside the package cannot read or modify components, so invariants such as 'the balance only changes through Deposit and Withdraw' hold for every client, forever.
Partial View and Full View
In the visible part, 'type Account is private;' gives clients the partial view: they know the type exists and can declare objects, assign them, compare them with = and pass them to the package's subprograms. After the keyword 'private' in the same specification comes the full view: 'type Account is record ... end record;'. The private part is physically in the spec (so the compiler knows the object size for clients) but logically hidden: only the package body and child packages may use it.
• type T is private; — partial view for clients. • private part: full declaration, visible only to the body and children. • Clients keep :=, = and /= (for non-limited private types).
Deferred Constants and Constructors
A constant of a private type cannot be given a value in the visible part because clients cannot see the components. Declare it as a deferred constant ('No_Account : constant Account;') and complete it in the private part. Since clients cannot write aggregates for a private type, the package provides constructor functions (Open) that return initialized values; component defaults in the full view guarantee that even default-initialized objects are valid.
• Deferred constant: C : constant T; completed in the private part. • Provide constructor functions; clients can't use aggregates. • Give full-view components defaults so every object starts valid.
Designing Abstract Data Types
The private type plus its operations forms an abstract data type (ADT). Keep the visible part minimal: expose queries (Balance, Owner) and commands (Deposit, Withdraw), raise package-defined exceptions on misuse, and add contracts (later lessons) such as Pre => Amount <= Balance (A). If you need to hide the size too (so changing the representation does not force client recompilation), make the full view an access to an incomplete type declared in the body (the 'Taft amendment' / pimpl idiom).
• ADT = private type + operations + exceptions (+ contracts). • Minimal visible part; state changes only through operations. • Taft-amendment types hide even the size (type Rep; type T is access Rep;).
Code Examples
Clients can declare, copy and compare Accounts but only change them through Deposit and Withdraw. Exception_Message retrieves the text attached with 'raise ... with'. Output: Owner 7 balance: 120 Copy = Mine? TRUE Mine = No_Account? FALSE Insufficient_Funds: balance 120 < 500
A client trying to modify a hidden component. Real GNAT diagnostics: l26_err.adb:6:04: error: invalid prefix "Mine" in selected component l26_err.adb:6:08: error: prefixed call is only allowed for objects of a tagged type unless -gnatX is used l26_err.adb:6:08: error: using -gnatX would make the prefixed call legal type Account is record... end record; -- in the visible part type Account is private;... private type Account is record... end record;
A visible record lets any client break invariants by assigning components.
No_Account : constant Account := (0, 0, 0); -- in the visible part
No_Account : constant Account; -- visible ... private No_Account : constant Account := (0, 0, 0);
The components are not visible yet; defer the constant's value to the private part.
type Account is private; -- full view with no defaults type Account is record Balance: Natural:= 0;...
Without defaults, objects declared by clients start with invalid data.
Who can see the full view of a private type? A. All clients B. The package body and child packages C. Only the main program D. Nobody
Which operations do clients get automatically on a (non-limited) private type? A. None B. Assignment and equality C. Component access D. Aggregates B.:= and = (and /=).
How is a constant of a private type declared in the visible part? A. Not possible B. As a deferred constant, completed in the private part C. With a pragma D. As a variable
Why is the private part in the spec instead of the body? A. Tradition B. So the compiler knows the representation/size when compiling clients C. For documentation D. For debugging Why does this client fail to compile?
Real GNAT output: l26_err.adb:6:04: error: invalid prefix "Mine" in selected component l26_err.adb:6:08: error: prefixed call is only allowed for objects of a tagged type unless -gnatX is used l26_err.adb:6:08: error: using -gnatX would make the prefixed call legal Balance is not visible to clients (GNAT also tries to interpret Mine.Balance as a prefixed call of the function Balance). Use the package's operations instead.
-- file: accounts.ads
package Accounts is
type Account is private; -- clients see only the name
No_Account : constant Account; -- deferred constant
Insufficient_Funds : exception;
function Open (Owner_Id : Positive; Initial : Natural) return Account;
procedure Deposit (A : in out Account; Amount : Positive);
procedure Withdraw (A : in out Account; Amount : Positive);
function Balance (A : Account) return Natural;
function Owner (A : Account) return Natural;
private
-- The full view: visible to the body and child packages, not to clients
type Account is record
Owner_Id : Natural := 0;
Balance : Natural := 0;
History : Natural := 0; -- number of transactions
end record;
No_Account : constant Account := (Owner_Id => 0, Balance => 0, History => 0);
end Accounts;
-- file: accounts.adb
package body Accounts is
function Open (Owner_Id : Positive; Initial : Natural) return Account is
((Owner_Id => Owner_Id, Balance => Initial, History => 1));
procedure Deposit (A : in out Account; Amount : Positive) is
begin
A.Balance := A.Balance + Amount;
A.History := A
…