Limited Types
So far, we discussed nonlimited types in most cases. In this chapter, we discuss limited types.
We can think of limited types as an easy way to avoid inappropriate semantics. For example, a lock should not be copied — neither directly, via assignment, nor with pass-by-copy. Similarly, a file, which is really a file descriptor, should not be copied. In this chapter, we'll see example of unwanted side-effects that arise if we don't use limited types for these cases.
Assignment and equality
Limited types have the following restrictions, which we discussed in the Introduction to Ada course:
copying objects of limited types via direct assignments is forbidden; and
there's no predefined equality operator for limited types.
(Of course, in the case of nonlimited types, assignments are possible and the equality operator is available.)
By having these restrictions for limited types, we avoid inappropriate side-effects for assignment and equality operations. As an example of inappropriate side-effects, consider the case when we apply those operations on record types that have components of access types:
package Nonlimited_Types is type Simple_Rec is private; type Integer_Access is access Integer; function Init (I : Integer) return Simple_Rec; procedure Set (E : Simple_Rec; I : Integer); procedure Show (E : Simple_Rec; E_Name : String); private type Simple_Rec is record V : Integer_Access; end record; end Nonlimited_Types;with Ada.Text_IO; use Ada.Text_IO; package body Nonlimited_Types is function Init (I : Integer) return Simple_Rec is begin return E : Simple_Rec do E.V := new Integer'(I); end return; end Init; procedure Set (E : Simple_Rec; I : Integer) is begin E.V.all := I; end Set; procedure Show (E : Simple_Rec; E_Name : String) is begin Put_Line (E_Name & ".V.all = " & Integer'Image (E.V.all)); end Show; end Nonlimited_Types;with Ada.Text_IO; use Ada.Text_IO; with Nonlimited_Types; use Nonlimited_Types; procedure Show_Wrong_Assignment_Equality is A, B : Simple_Rec := Init (0); procedure Show_Compare is begin if A = B then Put_Line ("A = B"); else Put_Line ("A /= B"); end if; end Show_Compare; begin Put_Line ("A := Init (0); A := Init (0);"); Show (A, "A"); Show (B, "B"); Show_Compare; Put_Line ("--------"); Put_Line ("Set (A, 2); Set (B, 3);"); Set (A, 2); Set (B, 3); Show (A, "A"); Show (B, "B"); Put_Line ("--------"); Put_Line ("B := A"); B := A; Show (A, "A"); Show (B, "B"); Show_Compare; Put_Line ("--------"); Put_Line ("Set (B, 7);"); Set (B, 7); Show (A, "A"); Show (B, "B"); Show_Compare; Put_Line ("--------"); end Show_Wrong_Assignment_Equality;
In this code, we declare the Simple_Rec
type in the
Nonlimited_Types
package and use it in the
Show_Wrong_Assignment_Equality
procedure. In principle, we're already
doing many things right here. For example, we're declaring the
Simple_Rec
type private, so that the component V
of access
type is encapsulated. Programmers that declare objects of this type cannot
simply mess up with the V
component. Instead, they have to call the
Init
function and the Set
procedure to initialize and change,
respectively, objects of the Simple_Rec
type. That being said, there are
two problems with this code, which we discuss next.
The first problem we can identify is that the first call to Show_Compare
shows that A
and B
are different, although both have the same
value in the V
component (A.V.all = 0
and B.V.all = 0
)
— this was set by the call to the Init
function. What's happening
here is that the A = B
expression is comparing the access values
(A.V = B.V
), while we might have been expecting it to compare the actual
integer values after dereferencing (A.V.all = B.V.all
). Therefore, the
predefined equality function of the Simple_Rec
type is useless and
dangerous for us, as it misleads us to expect something that it doesn't do.
After the assignment of A
to B
(B := A
), the information
that the application displays seems to be correct — both A.V.all
and B.V.all
have the same value of two. However, when assigning the
value seven to B
by calling Set (B, 7)
, we see that the value of
A.V.all
has also changed. What's happening here is that the previous
assignment (B := A
) has actually assigned access values
(B.V := A.V
), while we might have been expecting it to assign the
dereferenced values (B.V.all := A.V.all
). Therefore, we cannot simply
directly assign objects of Simple_Rec
type, as this operation changes
the internal structure of the type due to the presence of components of access
type.
For these reasons, forbidding these operations for the Simple_Rec
type
is the most appropriate software design decision. If we still need assignment
and equality operators, we can implement custom subprograms for the limited
type. We'll discuss this topic in the next sections.
In addition to the case when we have components of access types, limited types are useful for example when we want to avoid the situation in which the same information is copied to multiple objects of the same type.
In the Ada Reference Manual
Assignments
Assignments are forbidden when using objects of limited types. For example:
package Limited_Types is type Simple_Rec is limited private; type Integer_Access is access Integer; function Init (I : Integer) return Simple_Rec; private type Simple_Rec is limited record V : Integer_Access; end record; end Limited_Types;package body Limited_Types is function Init (I : Integer) return Simple_Rec is begin return E : Simple_Rec do E.V := new Integer'(I); end return; end Init; end Limited_Types;with Limited_Types; use Limited_Types; procedure Show_Limited_Assignment is A, B : Simple_Rec := Init (0); begin B := A; end Show_Limited_Assignment;
In this example, we declare the limited private type Simple_Rec
and two
objects of this type (A
and B
) in the
Show_Limited_Assignment
procedure. (We discuss more about limited
private types later).
As expected, we get a compilation error for the B := A
statement (in the
Show_Limited_Assignment
procedure). If we
need to copy two objects of limited type, we have to provide a custom procedure
to do that. For example, we can implement a Copy
procedure for the
Simple_Rec
type:
package Limited_Types is type Integer_Access is access Integer; type Simple_Rec is limited private; function Init (I : Integer) return Simple_Rec; procedure Copy (From : Simple_Rec; To : in out Simple_Rec); private type Simple_Rec is limited record V : Integer_Access; end record; end Limited_Types;package body Limited_Types is function Init (I : Integer) return Simple_Rec is begin return E : Simple_Rec do E.V := new Integer'(I); end return; end Init; procedure Copy (From : Simple_Rec; To : in out Simple_Rec) is begin -- Copying record components To.V.all := From.V.all; end Copy; end Limited_Types;with Limited_Types; use Limited_Types; procedure Show_Limited_Assignment is A, B : Simple_Rec := Init (0); begin Copy (From => A, To => B); end Show_Limited_Assignment;
The Copy
procedure from this example copies the dereferenced values of
From
to To
, which matches our expectation for the
Simple_Rec
. Note that we could have also implemented a
Shallow_Copy
procedure to copy the actual access values (i.e.
To.V := From.V
). However, having this kind of procedure can be dangerous
in many case, so this design decision must be made carefully. In any case,
using limited types ensures that only the assignment subprograms that are
explicitly declared in the package specification are available.
Equality
Limited types don't have a predefined equality operator. For example:
package Limited_Types is type Integer_Access is access Integer; type Simple_Rec is limited private; function Init (I : Integer) return Simple_Rec; private type Simple_Rec is limited record V : Integer_Access; end record; end Limited_Types;package body Limited_Types is function Init (I : Integer) return Simple_Rec is begin return E : Simple_Rec do E.V := new Integer'(I); end return; end Init; end Limited_Types;with Ada.Text_IO; use Ada.Text_IO; with Limited_Types; use Limited_Types; procedure Show_Limited_Equality is A : Simple_Rec := Init (5); B : Simple_Rec := Init (6); begin if A = B then Put_Line ("A = B"); else Put_Line ("A /= B"); end if; end Show_Limited_Equality;
As expected, the comparison A = B
triggers a compilation error because
no predefined =
operator is available for the Simple_Rec
type.
If we want to be able to compare objects of this type, we have to implement
the =
operator ourselves. For example, we can do that for the
Simple_Rec
type:
package Limited_Types is type Integer_Access is access Integer; type Simple_Rec is limited private; function Init (I : Integer) return Simple_Rec; function "=" (Left, Right : Simple_Rec) return Boolean; private type Simple_Rec is limited record V : Integer_Access; end record; end Limited_Types;package body Limited_Types is function Init (I : Integer) return Simple_Rec is begin return E : Simple_Rec do E.V := new Integer'(I); end return; end Init; function "=" (Left, Right : Simple_Rec) return Boolean is begin -- Comparing record components return Left.V.all = Right.V.all; end "="; end Limited_Types;with Ada.Text_IO; use Ada.Text_IO; with Limited_Types; use Limited_Types; procedure Show_Limited_Equality is A : Simple_Rec := Init (5); B : Simple_Rec := Init (6); begin if A = B then Put_Line ("A = B"); else Put_Line ("A /= B"); end if; end Show_Limited_Equality;
Here, the =
operator compares the dereferenced values of Left.V
and Right.V
, which matches our expectation for the Simple_Rec
type. Declaring types as limited ensures that we don't have unreasonable
equality comparisons, and allows us to create reasonable replacements when
required.
In other languages
In C++, you can overload the assignment operator. For example:
class Simple_Rec
{
public:
// Overloaded assignment
Simple_Rec& operator= (const Simple_Rec& obj);
private:
int *V;
};
In Ada, however, we can only define the equality operator (=
).
Defining the assignment operator (:=
) is not possible. The following
code triggers a compilation error as expected:
package Limited_Types is
type Integer_Access is access Integer;
type Simple_Rec is limited private;
procedure ":=" (To : in out Simple_Rec
From : Simple_Rec);
-- ...
end Limited_Types;
Limited private types
As we've seen in code examples from the previous section, we can apply
information hiding to limited types. In other words,
we can declare a type as limited private
instead of just limited
.
For example:
package Simple_Recs is type Rec is limited private; private type Rec is limited record I : Integer; end record; end Simple_Recs;
In this case, in addition to the fact that assignments are forbidden for
objects of this type (because Rec
is limited), we cannot access the
record components.
Note that in this example, both partial and full views of the Rec
record are of limited type. In the next sections, we discuss how the partial
and full views can have non-matching declarations.
In the Ada Reference Manual
Non-Record Limited Types
In principle, only record types can be declared limited, so we cannot use scalar or array types. For example, the following declarations won't compile:
package Non_Record_Limited_Error is type Limited_Enumeration is limited (Off, On); type Limited_Integer is new limited Integer; type Integer_Array is array (Positive range <>) of Integer; type Rec is new limited Integer_Array (1 .. 2); end Non_Record_Limited_Error;
However, we've mentioned in a previous chapter that private types don't have to be record types necessarily. In this sense, limited private types makes it possible for us to use types other than record types in the full view and still benefit from the restrictions of limited types. For example:
package Simple_Recs is type Limited_Enumeration is limited private; type Limited_Integer is limited private; type Limited_Integer_Array_2 is limited private; private type Limited_Enumeration is (Off, On); type Limited_Integer is new Integer; type Integer_Array is array (Positive range <>) of Integer; type Limited_Integer_Array_2 is new Integer_Array (1 .. 2); end Simple_Recs;
Here, Limited_Enumeration
, Limited_Integer
, and
Limited_Integer_Array_2
are limited private types that encapsulate an
enumeration type, an integer type, and a constrained array type, respectively.
Partial and full view of limited types
In the previous example, both partial and full views of the Rec
type
were limited. We may actually declare a type as limited private
(in the
public part of a package), while its full view is nonlimited. For example:
package Simple_Recs is type Rec is limited private; -- Partial view of Rec is limited private type Rec is record -- Full view of Rec is nonlimited I : Integer; end record; end Simple_Recs;
In this case, only the partial view of Rec
is limited, while its full
view is nonlimited. When deriving from Rec
, the view of the derived
type is the same as for the parent type:
package Simple_Recs.Child is type Rec_Derived is new Rec; -- As for its parent, the -- partial view of Rec_Derived -- is limited, but the full view -- is nonlimited. end Simple_Recs.Child;
Clients must nevertheless comply with their partial view, and treat the type as
if it is in fact limited. In other words, if you use the Rec
type in a
subprogram or package outside of the Simple_Recs
package (or its child
packages), the type is limited from that perspective:
with Simple_Recs; use Simple_Recs; procedure Use_Rec_In_Subprogram is R1, R2 : Rec; begin R1.I := 1; R2 := R1; end Use_Rec_In_Subprogram;
Here, compilation fails because the type Rec
is limited from the
procedure's perspective.
Limitations
Note that the opposite — declaring a type as private
and its full
full view as limited private
— is not possible. For example:
package Simple_Recs is type Rec is private; private type Rec is limited record I : Integer; end record; end Simple_Recs;
As expected, we get a compilation error in this case. The issue is that the partial view cannot be allowed to mislead the client about what's possible. In this case, if the partial view allows assignment, then the full view must actually provide assignment. But the partial view can restrict what is actually possible, so a limited partial view need not be completed in the full view as a limited type.
In addition, tagged limited private types cannot have a nonlimited full view. For example:
package Simple_Recs is type Rec is tagged limited private; private type Rec is tagged record I : Integer; end record; end Simple_Recs;
Here, compilation fails because the type Rec
is nonlimited in its full
view.
Limited and nonlimited in full view
Declaring the full view of a type as limited or nonlimited has implications in the way we can use objects of this type in the package body. For example:
package Simple_Recs is type Rec_Limited_Full is limited private; type Rec_Nonlimited_Full is limited private; procedure Copy (From : Rec_Limited_Full; To : in out Rec_Limited_Full); procedure Copy (From : Rec_Nonlimited_Full; To : in out Rec_Nonlimited_Full); private type Rec_Limited_Full is limited record I : Integer; end record; type Rec_Nonlimited_Full is record I : Integer; end record; end Simple_Recs;package body Simple_Recs is procedure Copy (From : Rec_Limited_Full; To : in out Rec_Limited_Full) is begin To := From; -- ERROR: assignment is forbidden because -- Rec_Limited_Full is limited in -- its full view. end Copy; procedure Copy (From : Rec_Nonlimited_Full; To : in out Rec_Nonlimited_Full) is begin To := From; -- OK: assignment is allowed because -- Rec_Nonlimited_Full is -- nonlimited in its full view. end Copy; end Simple_Recs;
Here, both Rec_Limited_Full
and Rec_Nonlimited_Full
are declared
as private limited
. However, Rec_Limited_Full
type is limited in
its full view, while Rec_Nonlimited_Full
is nonlimited. As expected,
the compiler complains about the To := From
assignment in the
Copy
procedure for the Rec_Limited_Full
type because its full
view is limited (so no assignment is possible). Of course, in the case of the
objects of Rec_Nonlimited_Full
type, this assignment is perfectly fine.
Limited private component
Another example mentioned by the Ada Reference Manual (7.3.1, 5/1) is about an array type whose component type is limited private, but nonlimited in its full view. Let's see a complete code example for that:
package Limited_Nonlimited_Arrays is type Limited_Private is limited private; function Init return Limited_Private; -- The array type Limited_Private_Array -- is limited because the type of its -- component is limited. type Limited_Private_Array is array (Positive range <>) of Limited_Private; private type Limited_Private is record A : Integer; end record; -- Limited_Private_Array type is -- nonlimited at this point because -- its component is nonlimited. -- -- The assignments below are OK: A1 : Limited_Private_Array (1 .. 5); A2 : Limited_Private_Array := A1; end Limited_Nonlimited_Arrays;package body Limited_Nonlimited_Arrays is function Init return Limited_Private is ((A => 1)); end Limited_Nonlimited_Arrays;with Limited_Nonlimited_Arrays; use Limited_Nonlimited_Arrays; procedure Show_Limited_Nonlimited_Array is A3 : Limited_Private_Array (1 .. 2) := (others => Init); A4 : Limited_Private_Array (1 .. 2); begin -- ERROR: this assignment is illegal because -- Limited_Private_Array is limited, as -- its component is limited at this point. A4 := A3; end Show_Limited_Nonlimited_Array;
As we can see in this example, the limitedness of the array type
Limited_Private_Array
depends on the limitedness of its component type
Limited_Private
. In the private part of Limited_Nonlimited_Arrays
package, where Limited_Private
is nonlimited, the array type
Limited_Private_Array
becomes nonlimited as well. In contrast, in the
Show_Limited_Nonlimited_Array
, the array type is limited because its
component is limited in that scope.
In the Ada Reference Manual
Tagged limited private types
For tagged private types, the partial and full views must match: if a tagged type is limited in the partial view, it must be limited in the full view. For example:
package Simple_Recs is type Rec is tagged limited private; private type Rec is tagged limited record I : Integer; end record; end Simple_Recs;
Here, the tagged Rec
type is limited both in its partial and full views.
Any mismatch in one of the views triggers a compilation error. (As an
exercise, you may remove any of the limited
keywords from the code
example and try to compile it.)
For further reading...
This rule is for the sake of dynamic dispatching and classwide types. The compiler must not allow any of the types in a derivation class — the set of types related by inheritance — to be different regarding assignment and equality (and thus inequality). That's necessary because we are meant to be able to manipulate objects of any type in the entire set of types via the partial view presented by the root type, without knowing which specific tagged type is involved.
Explicitly limited types
Under certain conditions, limited types can be called explicitly limited
— note that using the limited
keyword in a part of the declaration
doesn't necessary ensure this, as we'll see later.
Let's start with an example of an explicitly limited type:
package Simple_Recs is type Rec is limited record I : Integer; end record; end Simple_Recs;
The Rec
type is also explicitly limited when it's declared limited in
the private type's completion (in the package's private part):
package Simple_Recs is type Rec is limited private; private type Rec is limited record I : Integer; end record; end Simple_Recs;
In this case, Rec
is limited both in the partial and in the full view,
so it's considered explicitly limited.
However, as we've learned before,
we may actually declare a type as limited private
in the
public part of a package, while its full view is nonlimited. In this case, the
limited type is not considered explicitly limited anymore.
For example, if we make the full view of the Rec
nonlimited (by
removing the limited
keyword in the private part), then the Rec
type isn't explicitly limited anymore:
package Simple_Recs is type Rec is limited private; private type Rec is record I : Integer; end record; end Simple_Recs;
Now, even though the Rec
type was declared as limited private, the full
view indicates that it's actually a nonlimited type, so it isn't explicitly
limited.
Note that tagged limited private types are always explicitly limited types — because, as we've learned before, they cannot have a nonlimited type declaration in its full view.
In the Ada Reference Manual
Subtypes of Limited Types
We can declare subtypes of limited types. For example:
package Simple_Recs is type Limited_Integer_Array (L : Positive) is limited private; subtype Limited_Integer_Array_2 is Limited_Integer_Array (2); private type Integer_Array is array (Positive range <>) of Integer; type Limited_Integer_Array (L : Positive) is limited record Arr : Integer_Array (1 .. L); end record; end Simple_Recs;
Here, Limited_Integer_Array_2
is a subtype of the
Limited_Integer_Array
type. Since Limited_Integer_Array
is a
limited type, the Limited_Integer_Array_2
subtype is limited as well.
A subtype just introduces a name for some constraints on an existing type. As
such, a subtype doesn't change the limitedness of the constrained type.
We can test this in a small application:
with Simple_Recs; use Simple_Recs; procedure Test_Limitedness is Dummy_1, Dummy_2 : Limited_Integer_Array_2; begin Dummy_2 := Dummy_1; end Test_Limitedness;
As expected, compilations fails because Limited_Integer_Array_2
is a
limited (sub)type.
Deriving from limited types
In this section, we discuss the implications of deriving from limited types. As usual, let's start with a simple example:
package Simple_Recs is type Rec is limited null record; type Rec_Derived is new Rec; end Simple_Recs;
In this example, the Rec_Derived
type is derived from the Rec
type. Note that the Rec_Derived
type is limited because its ancestor is
limited, even though the limited
keyword doesn't show up in the
declaration of the Rec_Derived
type. Note that we could have actually
used the limited
keyword here:
type Rec_Derived is limited new Rec;
Therefore, we cannot use the assignment operator for objects of
Rec_Derived
type:
with Simple_Recs; use Simple_Recs; procedure Test_Limitedness is Dummy_1, Dummy_2 : Rec_Derived; begin Dummy_2 := Dummy_1; end Test_Limitedness;
Note that we cannot derive a limited type from a nonlimited ancestor:
package Simple_Recs is type Rec is null record; type Rec_Derived is limited new Rec; end Simple_Recs;
As expected, the compiler indicates that the ancestor Rec
should be of
limited type.
In fact, all types in a derivation class are the same — either limited or not. (That is especially important with dynamic dispatching via tagged types. We discuss this topic in another chapter.)
In the Ada Reference Manual
Deriving from limited private types
Of course, we can also derive from limited private types. However, there are more rules in this case than the ones we've seen so far. Let's start with an example:
package Simple_Recs is type Rec is limited private; private type Rec is limited null record; end Simple_Recs;package Simple_Recs.Ext is type Rec_Derived is new Rec; -- OR: -- -- type Rec_Derived is -- limited new Rec; end Simple_Recs.Ext;with Simple_Recs.Ext; use Simple_Recs.Ext; procedure Test_Limitedness is Dummy_1, Dummy_2 : Rec_Derived; begin Dummy_2 := Dummy_1; end Test_Limitedness;
Here, Rec_Derived
is a limited type derived from the (limited private)
Rec
type. We can verify that Rec_Derived
type is limited
because the compilation of the Test_Limitedness
procedure fails.
Deriving from non-explicitly limited private types
Up to this point, we have discussed explicitly limited types. Now, let's see how derivation works with non-explicitly limited types.
Any type derived from a limited type is always limited, even if the full view
of its ancestor is nonlimited. For example, let's modify the full view of
Rec
and make it nonlimited (i.e. make it not explicitly limited):
package Simple_Recs is type Rec is limited private; private type Rec is null record; end Simple_Recs;
Here, Rec_Derived
is a limited type because the partial view of
Rec
is limited. The fact that the full view of Rec
is nonlimited
doesn't affect the Rec_Derived
type — as we can verify with the
compilation error in the Test_Limitedness
procedure.
Note, however, that a derived type becomes nonlimited in the
private part or the body of a child package if it isn't explicitly limited.
In this sense, the derived type inherits the nonlimitedness of the parent's
full view. For example,
because we're declaring Rec_Derived
as is new Rec
in the child
package (Simple_Recs.Ext
), we're saying that Rec_Derived
is
limited outside this package, but nonlimited in the private part and body of
the Simple_Recs.Ext
package. We can verify this by copying the code from
the Test_Limitedness
procedure to a new procedure in the body of the
Simple_Recs.Ext
package:
package Simple_Recs.Ext with Elaborate_Body is -- Rec_Derived is derived from Rec, which is a -- limited private type that is nonlimited in -- its full view. -- -- Rec_Derived isn't explicitly limited. -- Therefore, it's nonlimited in the private -- part of Simple_Recs.Ext and its package -- body. -- type Rec_Derived is new Rec; end Simple_Recs.Ext;package body Simple_Recs.Ext is procedure Test_Child_Limitedness is Dummy_1, Dummy_2 : Rec_Derived; begin -- Here, Rec_Derived is a nonlimited -- type because Rec is nonlimited in -- its full view. Dummy_2 := Dummy_1; end Test_Child_Limitedness; end Simple_Recs.Ext;-- We copied the code to the -- Test_Child_Limitedness procedure (in the -- body of the Simple_Recs.Ext package) and -- commented it out here. -- -- You may uncomment the code to verify -- that Rec_Derived is limited in this -- procedure. -- -- with Simple_Recs.Ext; use Simple_Recs.Ext; procedure Test_Limitedness is -- Dummy_1, Dummy_2 : Rec_Derived; begin -- Dummy_2 := Dummy_1; null; end Test_Limitedness;
In the Test_Child_Limitedness
procedure of the Simple_Recs.Ext
package, we can use the Rec_Derived
as a nonlimited type because its
ancestor Rec
is nonlimited in its full view. (
As we've learned before, if a
limited type is nonlimited in its full view, we can copy objects of this type
in the private part of the package specification or in the package body.)
Outside of the package, both Rec
and Rec_Derived
types are
limited types. Therefore, if we uncomment the code in the
Test_Limitedness
procedure, compilation fails there (because
Rec_Derived
is viewed as descending from a limited type).
Deriving from tagged limited private types
The rules for deriving from tagged limited private types are slightly different than the rules we've seen so far. This is because tagged limited types are always explicitly limited types.
Let's look at an example:
package Simple_Recs is type Tagged_Rec is tagged limited private; private type Tagged_Rec is tagged limited null record; end Simple_Recs;package Simple_Recs.Ext is type Rec_Derived is new Tagged_Rec with private; private type Rec_Derived is new Tagged_Rec with null record; end Simple_Recs.Ext;with Simple_Recs.Ext; use Simple_Recs.Ext; procedure Test_Limitedness is Dummy_1, Dummy_2 : Rec_Derived; begin Dummy_2 := Dummy_1; end Test_Limitedness;
In this example, Rec_Derived
is a tagged limited type derived from the
Tagged_Rec
type. (Again, we can verify the limitedness of the
Rec_Derived
type with the Test_Limitedness
procedure.)
As explained previously, the derived type (Rec_Derived
) is a limited
type, even though the limited
keyword doesn't appear in its
declaration. We could, of course, include the limited
keyword in the
declaration of Rec_Derived
:
package Simple_Recs.Ext is type Rec_Derived is limited new Tagged_Rec with private; private type Rec_Derived is limited new Tagged_Rec with null record; end Simple_Recs.Ext;
(Obviously, if we include the limited
keyword in the partial view of
the derived type, we must include it in its full view as well.)
Deriving from limited interfaces
The rules for limited interfaces are different from the ones for limited tagged types. In contrast to the rule we've seen in the previous section, a type that is derived from a limited type isn't automatically limited. In other words, it does not inherit the limitedness from the interface. For example:
package Simple_Recs is type Limited_IF is limited interface; end Simple_Recs;package Simple_Recs.Ext is type Rec_Derived is new Limited_IF with private; private type Rec_Derived is new Limited_IF with null record; end Simple_Recs.Ext;with Simple_Recs.Ext; use Simple_Recs.Ext; procedure Test_Limitedness is Dummy_1, Dummy_2 : Rec_Derived; begin Dummy_2 := Dummy_1; end Test_Limitedness;
Here, Rec_Derived
is derived from the limited Limited_IF
interface. As we can see, the Test_Limitedness
compiles fine because
Rec_Derived
is nonlimited.
Of course, if we want Rec_Derived
to be limited, we can make this
explicit in the type declaration:
package Simple_Recs.Ext is type Rec_Derived is limited new Limited_IF with private; private type Rec_Derived is limited new Limited_IF with null record; end Simple_Recs.Ext;with Simple_Recs.Ext; use Simple_Recs.Ext; procedure Test_Limitedness is Dummy_1, Dummy_2 : Rec_Derived; begin Dummy_2 := Dummy_1; end Test_Limitedness;
Now, compilation of Test_Limitedness
fails because Rec_Derived
is
explicitly limited.
Immutably Limited Types
According to the Annotated Ada Reference Manual (7.5, 8.b/3), "an immutably limited type is a type that cannot become nonlimited subsequently in a private part or in a child unit." In fact, while we were talking about partial and full view of limited types, we've seen that limited private types can become nonlimited in their full view. Such limited types are not immutably limited.
The Annotated Ada Reference Manual also says that "if a view of the type makes it immutably limited, then no copying (assignment) operations are ever available for objects of the type. This allows other properties; for instance, it is safe for such objects to have access discriminants that have defaults or designate other limited objects." We'll see examples of this later on.
Immutably limited types include:
tagged limited types (i.e. with the keywords
tagged limited
);limited private type that have at least one access discriminant with a default expression;
task types, protected types, and synchronized interfaces;
any types derived from immutably limited types.
Let's look at a code example that shows instances of immutably limited types:
package Show_Immutably_Limited_Types is -- -- Explicitly limited type -- type Explicitly_Limited_Rec is limited record A : Integer; end record; -- -- Tagged limited type -- type Limited_Tagged_Rec is tagged limited record A : Integer; end record; -- -- Tagged limited private type -- type Limited_Tagged_Private is tagged limited private; -- -- Limited private type with an access -- discriminant that has a default -- expression -- type Limited_Rec_Access_D (AI : access Integer := new Integer) is limited private; -- -- Task type -- task type TT is entry Start; entry Stop; end TT; -- -- Protected type -- protected type PT is function Value return Integer; private A : Integer; end PT; -- -- Synchronized interface -- type SI is synchronized interface; -- -- A type derived from an immutably -- limited type -- type Derived_Immutable is new Explicitly_Limited_Rec; private type Limited_Tagged_Private is tagged limited record A : Integer; end record; type Limited_Rec_Access_D (AI : access Integer := new Integer) is limited record A : Integer; end record; end Show_Immutably_Limited_Types;package body Show_Immutably_Limited_Types is task body TT is begin accept Start; accept Stop; end TT; protected body PT is function Value return Integer is (PT.A); end PT; end Show_Immutably_Limited_Types;
In the Show_Immutably_Limited_Types
package above, we see multiple
instances of immutably limited types. (The comments in the source code indicate
each type.)
In the Ada Reference Manual
Non immutably limited types
Not every limited type is immutably limited. We already mentioned untagged private limited types, which can become nonlimited in their full view. In addition, we have nonsynchronized limited interface types. As mentioned earlier in this chapter, a type derived from a nonsynchronized limited interface, can be nonlimited, so it's not immutably limited.
In the Ada Reference Manual
Limited Types with Discriminants
In this section, we look into the implications of using discriminants with limited types. Actually, most of the topics mentioned here have already been covered in different sections of previous chapters, as well as in this chapter. Therefore, this section is in most parts just a review of what we've already discussed.
Let's start with a simple example:
package Simple_Recs is type Rec (L : Positive) is limited null record; end Simple_Recs;with Simple_Recs; use Simple_Recs; procedure Test_Limitedness is Dummy_1 : Rec (2); Dummy_2 : Rec (3); begin Dummy_2 := Dummy_1; -- ^^^^^^^^^^^^^^ -- ERRORS: -- 1. Cannot assign objects of -- limited types. -- 2. Cannot assign objects with -- different discriminants. end Test_Limitedness;
In this example, we see the declaration of the limited type Rec
, which
has the discriminant L
. For objects of type Rec
, we not only have
the typical restrictions that
equality and assignment aren't available,
but we also have the restriction that we won't be able to assign objects
with different discriminants.
In the Ada Reference Manual
Default Expressions
On the other hand, there are restrictions that apply to nonlimited types with discriminants, but not to limited types with discriminants. This concerns mostly default expressions, which are generally allowed for discriminants of limited types.
Discriminants of tagged limited types
As we've discussed previously, we can use default expressions for discriminants of tagged limited types. Let's see an example:
package Recs is type LTT (L : Positive := 1; M : Positive := 2) is tagged limited null record; end Recs;
Obviously, the same applies to tagged limited private types:
package Recs is type LTT (L : Positive := 1; M : Positive := 2) is tagged limited private; private type LTT (L : Positive := 1; M : Positive := 2) is tagged limited null record; end Recs;
In the case of tagged, nonlimited types, using default expressions in this context isn't allowed.
Access discriminant
Similarly, when using limited types, we can specify default expressions for access discriminants:
package Custom_Recs is -- Specifying a default expression for -- an access discriminant: type Rec (IA : access Integer := new Integer'(0)) is limited record I : Integer := IA.all; end record; end Custom_Recs;
In fact, as we've discussed before, this isn't possible for nonlimited types.
Note, however, that we can only assign a default expression to an access discriminant of an immutably limited type.
Discriminants of nontagged limited types
In addition to tagged limited types, we can use default expressions for discriminants of nontagged limited types. Let's see an example:
package Recs is type LTT (L : Positive := 1; M : Positive := 2) is limited null record; end Recs;
Obviously, the same applies to limited private types:
package Recs is type LTT (L : Positive := 1; M : Positive := 2) is limited private; private type LTT (L : Positive := 1; M : Positive := 2) is limited null record; end Recs;
Note that using default expressions for discriminants of nonlimited, nontagged types is OK as well.
Mutable subtypes and Limitedness
As we've mentioned before, an unconstrained discriminated subtype with defaults is called a mutable subtype. An important feature of mutable subtypes is that it allows changing the discriminants of an object, e.g. via assignments. However, as we know, we cannot assign to objects of limited types. Therefore, in essence, a type should be nonlimited to be considered a mutable subtype.
Let's look at a code example:
package Recs is type LTT (L : Positive := 1; M : Positive := 2) is limited null record; function Init (L : Positive; M : Positive) return LTT is ((L => L, M => M)); procedure Copy (From : LTT; To : in out LTT); end Recs;package body Recs is procedure Copy (From : LTT; To : in out LTT) is begin To := Init (L => From.L, M => From.M); -- ERROR: cannot assign to object of -- limited type To.L := From.L; To.M := From.M; -- ERROR: cannot change discriminants end Copy; end Recs;with Recs; use Recs; procedure Show is A : LTT; B : LTT := Init (10, 12); begin Copy (From => B, To => A); end Show;
As we can see in the Copy
procedure, it's not possible to properly
assign to the target object. Using Init
is forbidden because the
assignment is not initializing the target object — as we're not declaring
To
at this point. Also, changing the individual discriminants is
forbidden as well. Therefore, we don't have any means to change the
discriminants of the target object. (In contrast, if LTT
was a
nonlimited type, we would be able to implement Copy
by using the call to
the Init
function.)
Limited private type with unknown discriminants
We can declare limited private type with unknown discriminants. Because the discriminants are unknown, this is an indefinite type. Let's see an example:
package Limited_Private_Unknown_Discriminants is type Rec (<>) is limited private; private type Rec is limited record I : Integer; end record; end Limited_Private_Unknown_Discriminants;
In this example, we declare type Rec
, which has unknown discriminants.
Using a limited private type with unknown discriminants is an important Ada idiom, as we gain extra control over its initialization. This is explained in the Annotated Ada Reference Manual (3.7, 26.b/2)
"A subtype with unknown discriminants is indefinite, and hence an object of such a subtype needs explicit initialization. A limited private type with unknown discriminants is 'extremely' limited; objects of such a type can be initialized only by subprograms (either procedures with a parameter of the type, or a function returning the type) declared in the package. Subprograms declared elsewhere can operate on and even return the type, but they can only initialize the object by calling (ultimately) a subprogram in the package declaring the type. Such a type is useful for keeping complete control over object creation within the package declaring the type."
Therefore, in order to have useful applications for type Rec
from the
previous code example, we have to introduce a subprogram that initializes the
type. For example:
package Limited_Private_Unknown_Discriminants is type Rec (<>) is limited private; function Init return Rec; private type Rec is limited record I : Integer; end record; function Init return Rec is ((I => 0)); end Limited_Private_Unknown_Discriminants;with Limited_Private_Unknown_Discriminants; use Limited_Private_Unknown_Discriminants; procedure Show_Constructor_Function is R : Rec := Init; begin null; end Show_Constructor_Function;
In the Show_Constructor_Function
procedure from this
example, we call the Init
function to initialize the R
object in
its declaration (of Rec
type). Note that for this specific type, this is
the only possible way to declare the R
object. In fact, compilation
fails if we write R : Rec;
.
A function such as Init
is called a
constructor function for limited types.
We discuss this topic in more detail later on.
Record components of limited type
In this section, we discuss the implications of using components of limited type. Let's start by declaring a record component of limited type:
package Simple_Recs is type Int_Rec is limited record V : Integer; end record; type Rec is limited record IR : Int_Rec; end record; end Simple_Recs;
As soon as we declare a record component of some limited type, the whole record
is limited. In this example, the Rec
record is limited due to the
presence of the IR
component of limited type.
Also, if we change the declaration of the Rec
record from the previous
example and remove the limited
keyword, the type itself remains
implicitly limited. We can see that when trying to assign to objects of
Rec
type in the Show_Implicitly_Limited
procedure:
package Simple_Recs is type Int_Rec is limited record V : Integer; end record; type Rec is record IR : Int_Rec; end record; end Simple_Recs;with Simple_Recs; use Simple_Recs; procedure Show_Implicitly_Limited is A, B : Rec; begin B := A; end Show_Implicitly_Limited;
Here, the compiler indicates that the assignment is forbidden because the
Rec
type has a component of limited type. The rationale for this rule is
that an object of a limited type doesn't allow assignment or equality,
including the case in which that object is a component of some enclosing
composite object. If we allowed the enclosing object to be copied or tested for
equality, we'd be doing it for all the components, too.
In the Ada Reference Manual
Limited types and aggregates
Note
This section was originally written by Robert A. Duff and published as Gem #1: Limited Types in Ada 2005 and Gem #2.
In this section, we focus on using aggregates to initialize limited types.
Historically
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:
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; package Persons is type Limited_Person; type Limited_Person_Access is access all Limited_Person; type Limited_Person is limited record Name : Unbounded_String; Age : Natural; end record; end Persons;with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; with Persons; use Persons; procedure Show_Non_Aggregate_Init is X : Limited_Person; begin X.Name := To_Unbounded_String ("John Doe"); X.Age := 25; end Show_Non_Aggregate_Init;
which has the maintenance problem the full coverage rules are supposed to prevent. Or, make the type nonlimited, and gain the benefits of aggregates, but lose the ability to prevent copies.
Full coverage rules for limited types
Previously, we discussed full coverage rules for aggregates. They also apply to limited types.
Historically
The full coverage rules have been aiding maintenance since Ada 83. However, prior to Ada 2005, we couldn't use them for limited types.
Suppose we have the following limited type:
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; package Persons is type Limited_Person; type Limited_Person_Access is access all Limited_Person; type Limited_Person is limited record Self : Limited_Person_Access := Limited_Person'Unchecked_Access; Name : Unbounded_String; Age : Natural; Shoe_Size : Positive; end record; end Persons;
This type has a self-reference; it doesn't make sense to copy objects,
because 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.
We can initialize objects of limited type with an aggregate. Here, we can say:
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; with Persons; use Persons; procedure Show_Aggregate_Box_Init is X : aliased Limited_Person := (Self => <>, Name => To_Unbounded_String ("John Doe"), Age => 25, Shoe_Size => 10); begin null; end Show_Aggregate_Box_Init;
The Self => <>
means use the default value of
Limited_Person'Unchecked_Access
. Since Limited_Person
appears inside the type declaration, it refers to the "current instance"
of the type, which in this case is X
. Thus, we are setting