Assignment and equality
Limited types as parameter
Initialization and function return
Limited record elements
Private implementation of limited types
Limited types and aggregates
Full coverage rules
One interesting feature of Ada is the full coverage rules for aggregates. For example, suppose we have a record type:
We can create an object of the type using an aggregate:
The full coverage rules say that every component of
Person must be
accounted for in the aggregate. If we later modify type
adding a component:
and we forget to modify
X accordingly, the compiler will remind us.
Case statements also have full coverage rules, which serve a similar
Of course, we can defeat the full coverage rules by using
(usually for array aggregates and case statements, but occasionally useful
for record aggregates):
According to the Ada RM,
others here means precisely the same thing
Age | Shoe_Size. But that's wrong: what
means is "all the other components, including the ones we might add next
week or next year". That means you shouldn't use
you're pretty sure it should apply to all the cases that haven't been
Full coverage rules for limited types
The full coverage rules have been aiding maintenance since Ada 83. Since Ada 2005, however, we can also use them for limited types. Suppose we have the following limited type:
This type has a self-reference; it doesn't make sense to copy objects,
Self would end up pointing to the wrong place. Therefore,
we would like to make the type limited, to prevent developers from
accidentally making copies. After all, the type is probably private, so
developers using this package might not be aware of the problem. We could
also solve that problem with controlled types, but controlled types are
expensive, and add unnecessary complexity if not needed.
Prior to Ada 2005, aggregates were illegal for limited types. Therefore, we would be faced with a difficult choice: Make the type limited, and initialize it like this:
which has the maintenance problem the full coverage rules are supposed to prevent. Or, make the type non-limited, and gain the benefits of aggregates, but lose the ability to prevent copies.
Since Ada 2005, an aggregate is allowed to be limited; we can say:
We'll see what to do about that
Self => null soon.
One very important requirement should be noted: the implementation is
required to build the value of
X in place; it cannot construct
the aggregate in a temporary variable and then copy it into
because that would violate the whole point of limited objects —
you can't copy them.
It seems uncomfortable to set the value of
Self to the wrong value
null) and then correct it. It also seems annoying that we have a
(correct) default value for
Self, but prior to Ada 2005, we
couldn't use defaults with aggregates. Since Ada 2005, a new syntax in
aggregates is available:
<> means "use the default value, if any".
Here, we can say:
Self => <> means use the default value of
appears inside the type declaration, it refers to the "current instance"
of the type, which in this case is
X. Thus, we are setting
X.Self to be
Note that using
<> in an aggregate can be dangerous, because it can
leave some components uninitialized.
<> means "use the default
value". If the type of a component is scalar, and there is no
record-component default, then there is no default value.
For example, if we have an aggregate of type
String, like this:
we end up with a 10-character string all of whose characters are invalid values. Note that this is no more nor less dangerous than this:
As always, one must be careful about uninitialized scalar objects.
Constructor functions for limited types
Given that we can use 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.
Prior to Ada 2005, constructor functions (that is, functions that create new objects and return them) were not allowed for limited types. Since Ada 2005, fully-general constructor functions are allowed. Given the above, clients can say:
As for aggregates, the result of
Make_T is built in place (that is,
My_T), rather than being created and then copied into
My_T. Adding another level of function call, we can do:
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
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
component, and create the
all directly in
Rumplestiltskin_Is_My_Name is constant. Prior to
Ada 2005, it was impossible to create a constant limited object, because
there was no way to initialize it.
(<>) 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
an object of type
T is created, giving package
control over initialization of objects.
Ideally, limited and non-limited types should be just the same, except for the essential difference: you can't copy limited objects. By allowing functions and aggregates for limited types, we're 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. Prior to Ada 2005, that only worked for non-limited types. For limited types, the only way to construct on declaration was via default values, which limits you to one constructor. And the only way to pass parameters to that construction was via discriminants. Consider the following package:
Since Ada 2005, we can say:
whether or not
Set is limited.
This_Set : Set := Empty_Set;
seems clearer than:
which might mean "default-initialize to the empty set" or might mean "leave it uninitialized, and we'll initialize it in later".
This section was originally written by Robert A. Duff and published as Gem #10: Limited Types in Ada 2005.
Extended return statements for limited types
Previously, we discussed extended return statements. For most types, extended return statements are no big deal — it's just syntactic sugar. But for limited types, this syntax is almost essential:
The return statement here is illegal, because
Result is local to
Make_Task, and returning it would involve a copy, which makes no
sense (which is why task types are limited). Since Ada 2005, we can write
constructor functions for task types:
If we call it like this:
Result is created in place in
temporarily considered local to
Make_Task during the
-- some statements part, but as soon as
the task becomes more global.
My_Task really are
one and the same object.
When returning a task from a function, it is activated after the function
-- some statements part had better not try to call one
of the task's entries, because that would deadlock. That is, the entry
call would wait until the task reaches an accept statement, which will
never happen, because the task will never be activated.
Other usages of extended return statements
extended_return_statement was added to the language
specifically to support limited constructor functions, it comes in handy
whenever you want a local name for the function result:
Building objects from constructors
This section was originally written by Robert A. Duff and published as Gem #11: Limited Types in Ada 2005.
We've earlier seen examples of constructor functions for limited types similar to this:
It is useful to consider the various contexts in which these functions may be called. We've already seen things like:
in which case the limited object is built directly in a standalone object. This object will be finalized whenever the surrounding scope is left.
We can also do:
Here, the result of the function is built directly in the formal parameter
X will be finalized as soon as we
We can allocate initialized objects on the heap:
The result of the function is built directly in the heap-allocated object,
which will be finalized when the scope of
T_Ref is left (long after
We can create another limited type with a component of type
use an aggregate:
As usual, the function results are built in place, directly in
Outer_Obj.That, with no copying involved.
The one case where we cannot call such constructor functions is in an assignment statement:
which is illegal because assignment statements involve copying. Likewise, we can't copy a limited object into some other object:
This section was originally written by Robert A. Duff and published as Gem #12: Limited Types in Ada 2005.
Prior to Ada 2005, the following style was common:
Object_100 to be a default-initialized
Count equal to
100. It's a little bit annoying that we had
to write the default values
False twice. What if we
change our mind about
Red, and forget to change it in all the
Since Ada 2005, the
<> notation comes to the rescue. If we want to
100, but initialize
Is_Gnarly to their defaults", we can do this:
On the other hand, if we want to say, "make
but initialize all other components, including the ones we might add next
week, to their defaults", we can do this:
Note that if we add a component
Glorp : Integer; to type
others case leaves
Glorp undefined just as this
code would do:
Therefore, you should be careful and think twice before using