Limited Types, Build-in-Place & Extended Return
Prevent copying with limited and limited private types, understand what clients may and may not do with them, initialize limited objects with functions and aggregates (build-in-place), write extended return statements, and know when to make a type limited.
Some things must never be copied: a file handle, a lock, a network connection, a task, a unique hardware device. Declaring a type 'limited' removes assignment and predefined equality, so the compiler rejects accidental copies. Since Ada 2005, limited objects can still be initialized from functions and aggregates, because the result is built directly in the target object rather than copied.
limited and limited private
'type Handle is limited private;' — clients can declare objects and pass them as parameters, but cannot assign (:=) or compare (=) them unless the package explicitly provides an "=" function. A limited record with a visible full view ('type Lock is limited record ... end record;') allows component access but still forbids whole-object assignment. Any type containing a limited component is itself limited; tasks and protected objects are always limited.
• No := and no predefined = for limited types. • Objects can still be declared and passed as parameters (always by reference). • A type with a limited component is limited. • Tasks and protected types are limited.
Build-in-Place Initialization
Because copying is forbidden, a limited object can only be initialized by an aggregate or a function call whose result is created directly in the object being declared ('Printer : Handle := Acquire ("printer");'). Inside the function, the extended return statement 'return H : Handle := (...) do ... end return;' names the result object so you can finish initializing it step by step; it is constructed in place in the caller's object. Extended returns are also handy for non-limited types when the result needs several statements to build.
• Initialize limited objects from aggregates or function calls (build-in-place). • return R : T := ... do <statements> end return; — named result object. • Assignment after declaration remains illegal.
When to Use Limited Types
Make a type limited whenever copying would duplicate identity or ownership of a resource: files, locks, sockets, singletons, objects with pointers to themselves, and objects registered in a global table. Combine limited with controlled types (Ada.Finalization.Limited_Controlled) to release resources automatically, which gives Ada the RAII pattern.
• Limited = unique identity/ownership. • Limited_Controlled + Finalize = automatic resource release (RAII).
Code Examples
Acquire builds each Handle in place with an extended return and assigns a unique Id. The Lock record's components remain accessible. Output: Acquired # 1 (printer ) Acquired # 2 (scanner ) Printer # 1 used 2 times Scanner # 2 used 1 times Lock locked: TRUE
Copying and comparing limited objects. Real GNAT diagnostics: l27_err.adb:7:04: error: left hand of assignment must not be limited type l27_err.adb:8:09: error: there is no applicable operator "=" for private type "Handle" defined at resources.ads:4 H: Handle;... H:= Acquire ("x");
H : Handle := Acquire ("x");
Limited objects can be initialized at declaration but never assigned later.
if A = B then -- limited private Handle if Id (A) = Id (B) then
Limited types have no predefined equality; compare an identifying attribute or export an "=".
function Make return Handle is Tmp: Handle; begin... return Tmp; -- copies a limited object function Make return Handle is begin return H: Handle do... end return; end Make;
Returning an existing limited object would copy it; use an extended return.
Which operation is NOT available on a limited type? A. Declaring objects B. Passing as parameter C. Assignment D. Calling its operations
How can a limited object be initialized? A. It can't B. From an aggregate or function call at its declaration C. Only by a procedure D. By Unchecked_Conversion B. Build-in-place initialization.
What is 'return R : T do ... end return;'? A. A loop B. An extended return statement C. A task entry D. An exception handler
Which types are always limited? A. Records B. Tasks and protected types C. Arrays D. Access types Explain the errors.
Real GNAT output: l27_err.adb:7:04: error: left hand of assignment must not be limited type l27_err.adb:8:09: error: there is no applicable operator "=" for private type "Handle" defined at resources.ads:4
-- file: resources.ads
package Resources is
-- limited: no assignment and no predefined "=" for clients
type Handle is limited private;
function Acquire (Name : String) return Handle; -- built in place
procedure Use_It (H : in out Handle);
function Uses (H : Handle) return Natural;
function Id (H : Handle) return Positive;
-- A limited record type with a visible full view
type Lock is limited record
Locked : Boolean := False;
end record;
private
type Handle is limited record
Id : Positive;
Uses : Natural := 0;
Label : String (1 .. 8) := [others => ' '];
end record;
end Resources;
-- file: resources.adb
with Ada.Text_IO; use Ada.Text_IO;
package body Resources is
Next_Id : Positive := 1;
function Acquire (Name : String) return Handle is
begin
-- Extended return statement: the result object is built in place
return H : Handle := (Id => Next_Id, Uses => 0, Label => [others => ' ']) do
H.Label (1 .. Integer'Min (Name'Length, 8)) :=
Name (Name'First .. Name'First + Integer'Min (Name'Length, 8) - 1);
Put_Line ("Acquired #" & Positive'Image (H.Id) & " (" & H.Label &
…