Gem #3: Limited Types in Ada 2005 — Constructor Functions
by Bob Duff —AdaCore
Let's get started…
Given that Ada 2005 allows build-in-place aggregates for limited types, the obvious next step is to allow such aggregates to be wrapped in an abstraction — namely, to return them from functions. After all, interesting types are usually private, and we need some way for clients to create and initialize objects.
package P is type T (<>) is limited private; function Make_T (Name : String) return T; – constructor function private type T is limited record Name : Unbounded_String; My_Task : Some_Task_Type; My_Prot : Some_Protected_Type; end record; end P; package body P is function Make_T (Name : String) return T is begin return (Name => To_Unbounded_String (Name), others => <>); end Make_T; end P;
In Ada 95, constructor functions (that is, functions that create new objects and return them) are not allowed for limited types. Ada 2005 allows fully-general constructor functions. Given the above, clients can say:
My_T : T := Make_T (Name => "Bartholomew Cubbins");
As for aggregates, the result of Make_T is built in place (that is, in My_T), rather than being created and then copied into My_T. Adding another level of function call, we can do:
function Make_Rumplestiltskin return T is begin return Make_T (Name => “Rumplestiltskin”); end Make_Rumplestiltskin; Rumplestiltskin_Is_My_Name : constant T := Make_Rumplestiltskin;
It might help to understand the implementation model: In this case, Rumplestiltskin_Is_My_Name is allocated in the usual way (on the stack, presuming it is declared local to some subprogram). Its address is passed as an extra implicit parameter to Make_Rumplestiltskin, which then passes that same address on to Make_T, which then builds the aggregate in place at that address. Limited objects must never be copied! In this case, Make_T will initialize the Name component, and create the My_Task and My_Prot components, all directly in Rumplestiltskin_Is_My_Name.
Note that Rumplestiltskin_Is_My_Name is constant. In Ada 95, it is impossible to create a constant limited object, because there is no way to initialize it.
As in Ada 95, the “(<>)” on type T means that it has “unknown discriminants” from the point of view of the client. This is a trick that prevents clients from creating default-initialized objects (that is, “X : T;” is illegal). Thus clients must call Make_T whenever an object of type T is created, giving package P full control over initialization of objects.
Ideally, limited and nonlimited types should be just the same, except for the essential difference: you can't copy limited objects. Allowing functions and aggregates for limited types in Ada 2005 brings us very close to this goal. Some languages have a specific feature called “constructor”. In Ada, a “constructor” is just a function that creates a new object. Except that in Ada 95, that only works for nonlimited types. For limited types, the only way to “construct” on declaration is via default values, which limits you to one constructor. And the only way to pass parameters to that construction is via discriminants. In Ada 2005, we can say:
This_Set : Set := Empty_Set; That_Set : Set := Singleton_Set (Element => 42);
whether or not Set is limited. “This_Set : Set := Empty_Set;” seems clearer to me than:
This_Set : Set;
which might mean “default-initialize to the empty set” or might mean “leave it uninitialized, and we'll initialize it in later”.