!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); -- 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 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. ****************************************************************