Version 1.4 of ai05s/ai05-0130-1.txt
!standard 7.6(12) 09-06-26 AI05-0130-1/03
!standard 7.6.1(9/2)
!class ramification 09-01-16
!status ARG Approved 7-0-0 09-06-13
!status work item 08-12-04
!status received 08-10-17
!priority Low
!difficulty Medium
!qualifier Clarification
!subject Order of initialization/finalization of record extension components
!summary
The rules about order of initialization of record components are correct. This
can cause some extension components to be initialized before some ancestor
components.
!question
When a record contains two fields, one with an access discriminant and one
without, the field with the access discriminant is initialized last and
finalized first.
Does this also apply for record extensions? It would seem that the wording in
7.6(12) means that some of the components of a parent type could be required to
be initialized after the extension components. In the worst case, it could make
composition of initialization/finalization operations impossible (the operations
would have to be different for every extension in order to get the ordering right).
Is this the intent? (Yes.)
!response
The question is asking about an example like:
with Ada.Finalization;
package Pak1 is
type Rec0;
type Rec1 (D : access Rec0) is new
Ada.Finalization.Limited_Controlled with null record;
type Rec0 is tagged limited record
Comp1 : Rec1 (Rec0'access); --
end record;
type Rec2 is new Ada.Finalization.Limited_Controlled with null record;
procedure Initialize (X : in out Rec1);
procedure Finalize (X : in out Rec1);
procedure Initialize (X : in out Rec2);
procedure Finalize (X : in out Rec2);
type Rec4 is new Rec0 with record
Comp2 : Rec2; --
end record;
end Pak1;
with Ada.Text_IO;
package body Pak1 is
procedure Initialize (X : in out Rec1) is
begin
Ada.Text_IO.Put_Line ("Initialize Rec1");
end Initialize;
procedure Finalize (X : in out Rec1) is
begin
Ada.Text_IO.Put_Line ("Finalize Rec1");
end Finalize;
procedure Initialize (X : in out Rec2) is
begin
Ada.Text_IO.Put_Line ("Initialize Rec2");
end Initialize;
procedure Finalize (X : in out Rec2) is
begin
Ada.Text_IO.Put_Line ("Finalize Rec2");
end Finalize;
end Pak1;
with Pak1;
procedure Test1 is
X : Pak1.Rec4;
begin
null;
end Test1;
The language rules as currently constituted require that this program print:
Initialize Rec2
Initialize Rec1
Finalize Rec1
Finalize Rec2
The reason for this requirement is in order that the Initialize operation
for a component with a self-referential access discriminant can assume that other
components of the enclosing object have already been properly initialized
(AARM 7.6(12.b)). While that can't happen in this case, it is possible to
construct cases where an ancestor component has an Initialize operation
that does manage to access extension components. For instance, change Rec1.D
to a classwide type, and dispatch on it to an operation that uses the extension
components.
This requirement is counter-intuitive, as it means that an extension
component cannot assume that all of the ancestor components are initialized
(if there are any components constrained by access discriminants). The conceptual
model of extension components being added to the existing components without
any semantic changes is false in this case. That is a possible source of bugs.
But, clearly, the current rule also prevents bugs (just different bugs). In
the absence of any reason to choose one set of bugs over another, it makes
the most sense for the language to be consistent and use the same rules for
all types. Thus, we do not recommend a change to this rule.
Note that the rules as written potentially have an extra implementation cost
over the "composing" order. If the implementation uses a subprogram to initialize
components of a tagged type T, it would make sense to use that subprogram to
initialize the parent components of an extension of T (and just initialize the
extension components directly in T). But that would be complicated by this
requirement, and could not be done at all in unusual cases.
!ACATS Test
An ACATS C-test could be constructed to test a case like the example given here,
in order to verify that the rule is implemented properly.
However, we believe that this is relatively low priority to make portable. There
is clearly an implementation burden (and possible introduction of bugs) to fixing
this. So at this time, we are recommending not creating a test until after all
higher priority test cases are tested.
!appendix
!topic order of initialization/finalization of record components
!reference Ada 2005 RM 7.6(12), RM 7.6.1(9), AARM 7.6(12.b), AARM 7.6(9.a)
!from Dan Eilers 2005-10-17
!keywords initialize finalize access discriminant
!discussion
When a record contains two fields, one with an access discriminant and one
without, the field with the access discriminant is initialized last and
finalized first. The AARM explains:
7.6(12.b):
The fact that Initialize is done for components with access
discriminants after other components allows the Initialize operation
for a component with a self-referential access discriminant to assume
that other components of the enclosing object have already been
properly initialized.
7.6.1(9.a):
Reason: This allows the finalization of a component with an access
discriminant to refer to other components of the enclosing object
prior to their being finalized.
Is this rule supposed to apply even when the record is a type extension, and
the field with the access discriminant is in the ancestor part of the record?
The fields in the ancestor part should probably be initialized first and
finalized last, right?
with Ada.Finalization;
package Pak1 is
type Rec0;
type Rec1 (D : access Rec0) is new
Ada.Finalization.Limited_Controlled with null record;
type Rec0 is tagged limited record
Comp1 : Rec1 (Rec0'access); -- has access discriminant
end record;
type Rec2 is new Ada.Finalization.Limited_Controlled with null record;
procedure Initialize (X : in out Rec1);
procedure Finalize (X : in out Rec1);
procedure Initialize (X : in out Rec2);
procedure Finalize (X : in out Rec2);
type Rec4 is new Rec0 with record
Comp2 : Rec2; -- no access discriminant
end record;
end Pak1;
with Pak1;
procedure Test1 is -- related to c760012.a
X : Pak1.Rec4;
begin
null;
end Test1;
with Ada.Text_IO;
package body Pak1 is
procedure Initialize (X : in out Rec1) is
begin
Ada.Text_IO.Put_Line ("Initialize Rec1");
end Initialize;
procedure Finalize (X : in out Rec1) is
begin
Ada.Text_IO.Put_Line ("Finalize Rec1");
end Finalize;
procedure Initialize (X : in out Rec2) is
begin
Ada.Text_IO.Put_Line ("Initialize Rec2");
end Initialize;
procedure Finalize (X : in out Rec2) is
begin
Ada.Text_IO.Put_Line ("Finalize Rec2");
end Finalize;
end Pak1;
****************************************************************
From: Adam Beneschan
Sent: Thursday, December 18, 2008 7:52 PM
I wanted to point out that an important statement in the discussion of
AI05-0130-1 is, I believe, not quite correct.
The example looks essentially like:
type Rec0;
type Rec1 (D : access Rec0) is new
Ada.Finalization.Limited_Controlled with null record;
type Rec0 is tagged limited record
Comp1 : Rec1 (Rec0'access); -- has access discriminant
end record;
procedure Initialize (X : in out Rec1);
procedure Finalize (X : in out Rec1);
type Rec4 is new Rec0 with record
Comp2 : Rec2; -- a controlled type, no access discriminant
end record;
The language rules currently require that, for an object X of type Rec4, that
X.Comp2 be initialized before X.Comp1 and finalized after.
The discussion section of the AI contains this:
"But the reason for this requirement is in order that the Initialize operation for
a component with a self-referential access discriminant can assume that other
components of the enclosing object have already been properly initialized (AARM 7.6(12.b)).
Such a component cannot access (or even know about) components of any extensions, so
there does not appear to be any value to having this requirement apply to all of
the components of a type extension (rather than just to the extension components alone)."
The last sentence is correct in the above example. But change it to:
type Rec0 is tagged;
type Rec1 (D : access Rec0'Class) is new -- CHANGED!!!!!
Ada.Finalization.Limited_Controlled with null record;
type Rec0 is tagged limited record
Comp1 : Rec1 (Rec0'access); -- has access discriminant
end record;
procedure Initialize (X : in out Rec1);
procedure Finalize (X : in out Rec1);
type Rec4 is new Rec0 with record
Comp2 : Rec2; -- a controlled type, no access discriminant
end record;
and it's no longer correct, because now when Comp1 is initialized or finalized, the
user-defined Initialize or Finalize routine *could* access Comp2, via a dispatching call
on D.all (or by other means). So it would be a problem if the rules were changed to
allow Comp2 to be initialized after Comp1, or finalized before. E.g.:
type Rec0 is tagged;
type Rec1 (D : access Rec0'Class) is new
Ada.Finalization.Limited_Controlled with null record;
type Rec0 is tagged limited record
Comp1 : Rec1 (Rec0'access); -- has access discriminant
end record;
function Image_Of (X : in Rec0) return String;
procedure Initialize (X : in out Rec1);
procedure Finalize (X : in out Rec1);
type Rec4 is new Rec0 with record
Comp2 : Rec2; -- a controlled type, no access discriminant
end record;
overriding function Image_Of (X : in Rec4) return String;
procedure Finalize (X : in out Rec1) is
begin
Write_To_Log ("Finalizing Rec1 that refers to: " & Image_Of(X.D.all));
...
end Finalize;
Assuming that Image_Of (X : in Rec4) contains some information about X.Comp2, the above
Finalize routine would be calling Image_Of at a point after Comp2 were finalized, if the
language rules were changed.
****************************************************************
From: Randy Brukardt
Sent: Thursday, December 18, 2008 8:07 PM
Thanks, Adam, for this example, I obviously didn't think of it. Did you tell Dan?? :-)
[This was originally his question.]
Specifically, with this additional example, I no longer understand precisely what change
he was recommending. Moreover, I can't think of a good reason for changing the rules for
*some* extension types, but not *all* extension types. If the compiler can't use composition
in some cases, there's little reason to go out of our way allow it in other cases -- all of
the support code needed to handle non-composing initialization must necessarily already exist
in the compiler. Once you've gone to all of that work, there doesn't seem to be any reason
not to use it always. (And, of course, rules that change because of the contents of some
construct are a maintenance hazard.)
As such, I will probably rewrite it as a confirmation (the existing rules are correct),
unless someone explains better than I can why that would be a bad idea.
****************************************************************
From: Dan Eilers
Sent: Monday, January 5, 2009 8:22 PM
My main concern is that the existing rules are somewhat counter-intuitive, in that they imply
that an ancestor may not be fully initialized before its descendants. This could conceivably
cause problems if the initializations have side-effects, and the order of the side-effects
matters. Adam has a good point, that changing this can also cause problems. So I don't feel
strongly about how this is decided.
In any case, there should be an ACATS test, especially considering that implementations currently
differ. There should also probably be an RM note.
p.s.
I think access discriminants are often used as workarounds for missing language features, and
their problems could be minimized by providing direct language support for controlled types as
interfaces, and by-reference IN parameters with modifiable state, etc.
****************************************************************
Questions? Ask the ACAA Technical Agent