Version 1.1 of ai05s/ai05-0142-1.txt

Unformatted version of ai05s/ai05-0142-1.txt version 1.1
Other versions for file ai05s/ai05-0142-1.txt

!standard 6.5          09-02-14 AI05-0142-1/01
!class Amendment 09-02-14
!status work item 09-02-14
!status received 09-01-19
!priority Medium
!difficulty Medium
!subject Variable function results
!summary
(See proposal.)
!problem
Modifying a portion of a larger opaque object (such as a container) is not well-supported in Ada. The Ada.Containers packages provide a procedure Replace_Element for this purpose. But this procedure requires copying the element (potentially in both directions). That could be very expensive if the element is large.
The Ada.Containers packages also provide an procedure Update_Element for this purpose; this provides a writable object as a parameter to a subprogram passed into the procedure. This procedure avoids the need to copy the element, but it is hardly convenient to define a procedure for every component of the element that needs changing. The extra syntax needed obscures the real meaning of the program.
An option that was rejected for the Ada.Containers packages was to return an access to the element type. However, this is problematic as it is difficult to control the accessibility and lifetime of the returned access. If the element is removed from the container, the returned access could become dangling; continued use of the access would make the program erroneous. Moreover, the accessibility of the returned object (and thus what could be done with it) would depend on the actual implementation of the container. Bounded containers would typically only return access values with a very short lifetime, while unbounded containers would typically return access values with a much longer lifetime. Converting from an unbounded to bounded form could thus introduce new runtime errors - a serious maintenance hazard.
What really is needed is to be able to return a variable from a function, a variable view whose lifetime is defined to be exactly the same as that of the function call.
!proposal
Add an "in out" mode to a function result. This would have a meaning as close to "in out" parameter passing as possible. In particular, the mode would mean that a (view of) a variable is returned. The view would have the accessibility of the function call, and could not be used afterwards.
Similarly, the "in out" mode for a function return would use return-by-reference (return by-copy/result could be supported, see the !discussion).
An accessibility check would need to be made at the point of the return; anything that had a lifetime at least as long as the function call (including its parameters) could be returned, but not local objects.
!wording
** TBD **
!discussion
This proposal is very similar to the old return-by-reference functions of Ada 95, except that it is allowed for all kinds of types. The primary problems with them were that they were not allowed for all types.
The big advantage of this proposal is that any variable can be returned, without cluttering the syntax with access types and aliased declarations. Moreover, the resulting view can be directly assigned, selected, etc. Indeed, this proposal would give the illusion of a user-defined array indexing operation:
function Index (Obj : in out Container; I : in Natural) return Element_Type;
could be used (assuming Container is tagged) as
My_Container.Index(10) := Some_Element; and Some_Element := My_Container.Index(5);
The short lifetime of the variable view would prevent almost all dangling references. An attempt to take an 'Access of a component of the object would almost always fail (the object itself would not be defined as aliased so 'Access would not be allowed on it). Another task could deallocate the underlying object, and that would make using a returned variable erroneous (by existing rules). Similarly, renaming this variable view would allow other operations to be executed while the variable view still existed. For a container, this could allow a call to Delete the element, also leading to erroneous execution (if the element's memory was freed) or just junk (if the element was reused).
One solution to the latter problem would be to make renaming of such functions illegal, but that would be quite inconsistent with the rest of the language.
Note that if a container library only allowed this syntax in a tampering context (where a tampering context is declared by an object or a call-back as it currently is, making it scoped), then such a routine is safe.
Alternative syntaxes:
If we don't extend the language to allow "in out" parameters on functions, allowing them on the result would be really, really weird. But the capability would still be useful for returning parts of heap allocated objects (such as elements in an unbounded container) and for returning parts of access parameters (both named and anonymous). In that case, we'd need another syntax.
One possibility is:
function Element (Obj : in Container) return not constant Element_Type;
(Aside: for this syntax, we'd really rather use
function Element (Obj : in Container) return constant Element_Type; for our current meaning and function Element (Obj : in Container) return Element_Type; for our new feature as that would be consistent with the rest of the language. But that is way too incompatible.)
Another possibility is a new keyword:
function Element (Obj : in Container) return variable Element_Type;
Termination alternative:
An alternative to erroneousness for cases where the object whose view is returned ceases to exist while the function return is still usable would be to have some sort of call-back that occurs when the returned view ceases to exist. In this case, the accessor function itself can become a tampering context - setting the marker when called and clearing it when the view ceases to exist.
This model is consistent with by-copy returning of objects (defined consistently with parameter passing); in that case such a call-back to do the copying back to the original object would be required at the point that the variable view ceases to exist (the end of the master of the function call). It is only a small step to making that call-back something that can be used by the programmer.
This could take the form of an extended extended_return statement:
return defining_identifier : [aliased] return_subtype_indication [:= expression] [do
handled_sequence_of_statements
[at end handled_sequence_of_statements] end return];
The idea being that the "at end" statements are executed when the return object or view ceases to exist. Indeed, a capability like this for access returns (along with some way to control the accessibility of the result to the caller level) would be sufficient to solve the major problem of safely returning a value. But it seems too radical (and relatively expensive if not indicated in the profile of the function somehow) to make as an integral part of this proposal.
User-defined dereference:
Such a variable-returning function is essentially a user-defined dereference. So an additional feature is not really needed. But, if we wanted to, we could define one easily using this feature:
function "all" (Ptr_Like : in Private_Pointer) return in out Designated_Type; It could then be used in the normal way: P : Private_Pointer; ... P.all := <some aggregate>; Some_Procedure (P.all);
This would make it possible to define controlled types which worked exactly like access types (and presumably would use the finalization events to do appropriate storage management).
Acknowledgement: The basic idea for this feature came from a comp.lang.ada discussion, and from Dmitry Kazakov in particular. It's surely somewhat different than that initial idea; blame the author, not the idea, for any problems.
!examples
In the existing containers libraries, this would be used as something like:
function Modifiable_Element (Container : in out Vector; Position : in Cursor) return in out Element_Type;
Note that the name "Element" has already been used by the containers libraries, and it is fact was used for a function with a type-conforming profile, so using that name would be inadvisable. Moreover, the fact that the container is "in out" when it can be modified (a standard design feature of the containers libraries) suggests that a different name be used so that it is still possible to read "in" parameter containers.
This could be used (based on a recent example from comp.lang.ada):
with Ada.Containers.Vectors; procedure Check is package Integer_Vectors is new Ada.Containers.Vectors (Index_Type => Natural, Element_Type => Integer);
package Nested_Vectors is new Ada.Containers.Vectors (Index_Type => Natural, Element_Type => Integer_Vectors.Vector, "=" => Integer_Vectors."=");
IV : Integer_Vectors.Vector; NV : Nested_Vectors.Vector; begin IV.Append(42); NV.Append(IV); NV.Modifiable_Element(0).Append(43); end Check;
!ACATS test
!appendix

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

Questions? Ask the ACAA Technical Agent