Version 1.1 of acs/ac-00193.txt

Unformatted version of acs/ac-00193.txt version 1.1
Other versions for file acs/ac-00193.txt

!standard B.1          10-02-22 AC95-00193/01
!class confirmation 10-02-22
!status received no action 10-02-22
!status received 10-02-12
!subject 'Input on types that cannot be default initialized
!summary
!appendix

!topic 'Input on types that cannot be default-initialized
!reference 13.13.2(27)
!from Adam Beneschan 10-02-12
!discussion

In Ada 2005, it's pretty easy to define a type that cannot be initialized by
default: for example, declare a record Rec with a component that is a
null-excluding access type with no initial expression.

   type Rec is record
      Comp : not null access Some_Other_Rec;
   end record;
   X : Rec;  -- will raise Constraint_Error

Any object declaration of type Rec must have an initial expression, or else a
Constraint_Error will be raised at runtime.

(In a sense, you could do this in Ada 95 by declaring a record type with a
component whose initial expression calls a function that always raises an
exception, but this would be pretty obscure.)

It appears that this means Rec'Input cannot be used at all, since the semantics
(13.13.2(27)) specify that an object of that type is created and
default-initialized, and then passed to 'Read.

Is this desirable?

****************************************************************

From: Randy Brukardt
Date: Friday, February 12, 2010  8:56 PM

> Is this desirable?

No, it is not desirable to declare a type like this. :-)

Seriously, this looks like a language-lawyer question to me. I've tried to write
types like this, but it is virtually never the case that an access component
will never take on the value null. One example is our implementation of
Unbounded_Strings:

    type Unbounded_String is new Ada.Finalization.Controlled with record
        Data : not null access String := new String(1..0);
    end record;

This would have been a fine way to write these, except that it doesn't work:
the finalization routine has to free the allocated storage, and what can it put
in there instead? Pretty much the only choice is "null".

So I don't think null exclusions can be used very often outside of parameters
and other short-lived views. (They of course are *very* useful for that.)

Thus it doesn't matter that much what happens in this particular case for
'Input.

Also note that there are a lot of things that you cannot do with such a type.
You can't use "<>" in an aggregate, you can't declare a stand-alone object, etc.
If you really need such a type, declaring it without an unknown discriminant to
prevent uninitialized declaration is just being nasty to whomever uses the type.

(I then went on about 'Input not making sense for such types, forgetting that
they're not legal in a stand-alone context. And for a partial view with unknown
discriminants, the full view might in fact work. I still think this is a bug,
but obviously not one we're going to change.)

---

Anyway, to directly address your concern, what exactly is the alternative?
We can't allow passing uninitialized junk to T'Read (which might be user-defined
and might very well depend on some values in the initial record). Because of the
history here, that would be the worst kind of incompatibility: code that used to
be legal and work becoming erroneous (if accessing an uninitialized composite
component). Maybe that isn't very likely, but it's surely legal, and it would be
impossible to detect the problem in any sort of automated way. (Well, maybe an
ultra-fancy tool could detect it, but it would have to trace through parameter
passing and the like to verify that no reading of parts happens in the
user-defined overriding. Not terribly likely to get built, I think.)

We can't not call T'Read (because it might be user-defined). (If it's *not*
user-defined, we'd have some more flexibility. But it would be weird to have
rules so that only the predefined T'Input could work in this case. There is too
much magic in the stream attributes as it is.)

The notion of inventing "default initialization without checks" just for this
case is completely unpalatable. (And it too could cause some incompatibilities,
although those would be best described as bugs.)

So, in the absence of a better idea, we pretty much have to accept this behavior
in an unlikely case.

****************************************************************

From: Adam Beneschan
Date: Friday, February 12, 2010  9:29 PM

> Seriously, this looks like a language-lawyer question to me.

It came up in user code, more or less.  What happened was that the code used a
null-excluding access type as the Element_Type of an instance of
Ada.Containers.Indefinite_Hashed_Maps.  The GNAT implementation of that package
defines stream routines for the Map type, and the Map'Read routine ultimately
relies on Element_Type'Input.  So a program that instantiates this container
package, using a null-excluding access type (or something with a null-excluding
access type as a component), can't read a Map using the 'Read or 'Input
attributes.  (However, I don't think this particular program was going to try to
do that.)

It's occurred to me that null-excluding access types can be a problem for
generics in any case.  (Probably it's occurred to others too.)  If you use of
them as the actual for a generic formal T, it's a problem because you don't
really know whether the body of the generic tries to declare an object of type T
(with no initializer).  In this case, that can't happen because the generic
formal type is indefinite, unless 'Input is involved.  (I note that GNAT's
implementation of Ada.Containers.Hashed_Maps will have that problem even though
it doesn't use 'Input; in that case, Map'Read will fail when it tries to
allocate an uninitialized record that has an Element_Type as a component.)

So it seems that there is a potential problem there, but you're probably right
that there's no decent solution.

****************************************************************

From: Randy Brukardt
Date: Friday, February 12, 2010  9:51 PM

...
> It came up in user code, more or less.  What happened was that the
> code used a null-excluding access type as the Element_Type of an
> instance of Ada.Containers.Indefinite_Hashed_Maps.  The GNAT
> implementation of that package defines stream routines for the Map
> type, and the Map'Read routine ultimately relies on
> Element_Type'Input.  So a program that instantiates this container
> package, using a null-excluding access type (or something with a
> null-excluding access type as a component), can't read a Map using the
> 'Read or 'Input attributes.  (However, I don't think this particular
> program was going to try to do that.)

If they actually wanted to do it, creating their own user-defined 'Input would
surely fix the problem. (It would have to use an extended return with an
initialized object, but that seems possible, especially if they don't need to
call the original 'Read. Probably would need a conversion from a non-null
excluding subtype, but that is legal assuming that general access types are
involved.) So it isn't a complete show-stopper, just somewhat less convenient
than it could be.

...
> It's occurred to me that null-excluding access types can be a problem
> for generics in any case.  (Probably it's occurred to others too.)  If
> you use of them as the actual for a generic formal T, it's a problem
> because you don't really know whether the body of the generic tries to
> declare an object of type T (with no initializer).  In this case, that
> can't happen because the generic formal type is indefinite, unless
> 'Input is involved.  (I note that GNAT's implementation of
> Ada.Containers.Hashed_Maps will have that problem even though it
> doesn't use 'Input; in that case, Map'Read will fail when it tries to
> allocate an uninitialized record that has an Element_Type as a
> component.)

Right, like I said, using a null-excluding type for anything other than a
short-lived view is likely to get you into trouble sooner or later.

It would be nice if types like your original record type could be explicitly
declared to have unknown discriminants, so that default initialization of them
because illegal. (Then you could at least detect the error early - it's silly to
have detect something obvious like this at runtime.)

****************************************************************

[Editor's note: Streaming of access types using the default implementation
of 'Read/'Input is dubious in any case. 13.13.2(35/2) says such values can
be abnormal, and thus cannot be depended upon.]

****************************************************************

Questions? Ask the ACAA Technical Agent