Gem #1: Limited Types in Ada 2005 — Limited Aggregates

by Bob Duff —AdaCore

Let's get started…

One of my favorite features of Ada is the “full coverage rules” for aggregates. For example, suppose we have a record type:

   type Person is
      record
         Name : Unbounded_String;
         Age : Years;
      end record;

We can create an object of the type using an aggregate:

   X : constant Person :=
      (Name => To_Unbounded_String (”John Doe”),
       Age => 25);

The full coverage rules say that every component of Person must be accounted for in the aggregate. If we later modify type Person by adding a component:

   type Person is
      record
         Name : Unbounded_String;
         Age : Natural;
         Shoe_Size : Positive;
      end record;

and we forget to modify X accordingly, the compiler will remind us. Case statements also have full coverage rules, which serve a similar purpose.

Of course, we can defeat the full coverage rules by using “others” (usually for array aggregates and case statements, but occasionally useful for record aggregates):

   X : constant Person :=
      (Name => To_Unbounded_String (”John Doe”),
       others => 25);

According to the Ada RM “others” here means precisely the same thing as “Age | Shoe_Size”. But that's wrong: what “others” really means is “all the other components, including the ones we might add next week or next year”. That means you shouldn't use “others” unless you're pretty sure it should apply to all the cases that haven't been invented yet.

So far, this is old news — the full coverage rules have been aiding maintenance since Ada 83. So what does this have to do with Ada 2005?

Suppose we have a limited type:

   type Limited_Person is limited
      record
         Self : Limited_Person_Access := Limited_Person'Unchecked_Access;
         Name : Unbounded_String;
         Age : Natural;
         Shoe_Size : Positive;
      end record;

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 programmers from accidentally making copies. After all, the type is probably private, so the client programmer 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.

In Ada 95, aggregates were illegal for limited types. Therefore, we would be faced with a difficult choice: Make the type limited, and initialize it like this:

   X : Limited_Person;
   X.Name := To_Unbounded_String ("John Doe");
   X.Age := 25;

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.

In Ada 2005, an aggregate is allowed to be limited; we can say:

   X : aliased Limited_Person :=
      (Self => null, – Wrong!

       Name => To_Unbounded_String (”John Doe”),
       Age => 25,
       Shoe_Size => 10);
   X.Self := X'Access;

We'll see what to do about that “Self => null” in a future gem.

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 X, because that would violate the whole point of limited objects — you can't copy them