# Returning compound types from subprograms Returning a larger data type like a record is different from returning a simple value which usually fits into a CPU register. By default – and unless explicitly enforced by using dynamically allocated records and access types – Ada always uses **value semantics** to return compound types. This means, the caller *always gets data that is valid after the subprogram returns and everything the subprogram has allocated on its stack becomes invalid*. Such data must either be copied to the caller or must not be allocated on the subprogram's private stack at all. An option is to create the data in-place, that is, in a memory space available to both the caller and the called subprogram. Either way, as a user you normally do not need to care about such semantics. The compiler does this transparently for you and you can assume that a subprogram returning a ``record`` type will return a **valid** instance of that type and not some dangling reference or pointer to a memory location which may (or may not) hold valid data. Enforcing this is not always simple and may involve additional copy operations between caller and callee, and some optimizations can be performed to eliminate such costly operations. Particularly for larger record types. For the following observations, we assume a compound type in the following form: ```{code-block} ada --- linenos: 1 caption: Record definition --- type Point is tagged record X, Y, Z : Long_Float := 0.0; end record; ``` ## Aggregate notation In aggregate notation, the subprogram implicitly constructs a record of the required type in its return statement. ```{code-block} ada --- linenos: 1 caption: Aggregate return statement --- -- construct a new Point from a given Point and an offset function Move_Point(p : Point; xdelta : Long_Float) return Point is begin -- return an intialized Point record. This follows the same -- syntax as p : Point := (...) declarations. return (X => p.X + xdelta, Y => p.Y, Z => p.Z); end Move_Point; ``` ## Extended return statement [This](https://www.adacore.com/blog/ada-gem-10) is a feature that was introduced in Ada 2005. It allows to declare and populate a return value in a loop or other block construct. ## Using an out parameter Another option and especially useful for procedures that do not allow a return value or for subprograms that must return multiple results are ``out`` parameters. The caller initializes a matching data type and passes it as ``out`` parameter to the subprogram. The subprogram then populates the record and optionally can return another value (for example, a success or failure code indicating the validity of the returned data). This method also does not require any copy operations, because the caller must set up the record receiving the result. An ``out`` parameter (and likewise ``in out`` parameters) is passed as a reference, so no copy operation is involved here either. The subprogram can directly write to the provided memory location holding the target record structure. ```{code-block} ada --- linenos: 1 caption: Using out parameter --- -- construct a new Point from a given Point and an offset function Move_Point(p : Point; rslt : out Point; xdelta : Long_Float) return Boolean is success : Boolean := False; begin rslt.X := p.X + xdelta; rslt.Y := p.Y; rslt.Z := p.Z; success := True; return (success); end Move_Point; ``` ## Avoiding excessive copying operations Copying around larger compound types can be time consuming and it is therefore desirable to minimize or even completely avoid such operations. Modern compilers (particularly C++, Rust, but also Ada) use strategies and methods commonly known as **Return Value Optimization (RVO)** or **Copy Elision** [^rf1]. Such optimizations usually depend on the level of compiler optimization and may not be used for non-optimized debug builds. For GCC compilers, they will be active beginning at ``–O1`` optimization level and will be used more aggressively at ``—O2`` or ``–O3``. ### The problem Consider a subprogram returning a large record. The record is first allocated on the stack and constructed during the subprogram executes. It then needs to be returned to the calling entity and since the subprogram's stack is no longer valid after it returns, a copy operation is needed to give the calling entity access to a valid copy of the data. To solve this issue with **RVO** the compiler may allocate enough space on the stack of the *calling entity* and the subprogram will then construct the compound type not on its own, but on the caller's stack. The calling entity can then use the *„returned“* data structure (which, in reality, wasn't returned, but built in-place of the calling entity) without performiny a copy operation. In C++, the standard requires or even guarantees that Copy Elision is performed, the Ada compiler prioritizes code correctness and **may** not use such techniques in case they would violate correct behavior. [^rf2]. ```{tags} ada ``` [^rf1]: See [here for an introduction](https://www.pass4sure.com/blog/introduction-to-copy-elision-and-return-value-optimization-in-c/). While it describes the technology from a C++ point of view, the concepts apply to other languages. The GNAT compiler is based on the GCC backend and can thus use similar techniques, but the Ada frontend may overrule in order to maintain code correctness. [^rf2]: This is more or less a theoretical construct. In most cases, any form of RVO should not endanger correct code behavior.