# Subprograms

## Parameter Modes and Associations

Todo

Brief section on parameter modes:

• Mention by-copy and by-reference types

• Mention: parameter modes, formal and actual parameters

• Add link to section on "Aliased Parameters"

Relevant topics

## Operators

Operators are commonly used for variables of scalar types such as `Integer` and `Float`. In these cases, they replace usual function calls. (To be more precise, operators are function calls, but written in a different format.) For example, we simply write `A := A + B + C;` when we want to add three integer variables. A hypothetical, non-intuitive version of this operation could be `A := Add (Add (A, B), C);`. In such cases, operators allow for expressing function calls in a more intuitive way.

Many primitive operators exist for scalar types. We classify them as follows:

Category

Operators

Logical

`and`, `or`, `xor`

Relational

`=`, `/=`, `<`, `<=`, `>`, `>=`

`+`, `-`

`+`, `-`, `&`

Multiplying

`*`, `/`, `mod`, `rem`

Highest precedence

`**`, `abs`, `not`

In the Ada Reference Manual

### User-defined operators

For non-scalar types, not all operators are defined. For example, it wouldn't make sense to expect a compiler to include an addition operator for a record type with multiple components. Exceptions to this rule are the equality and inequality operators (`=` and `/=`), which are defined for any type (be it scalar, record types, and array types).

For array types, the concatenation operator (`&`) is a primitive operator:

package Integer_Arrays is type Integer_Array is array (Positive range <>) of Integer; end Integer_Arrays;
with Ada.Text_IO; use Ada.Text_IO; with Integer_Arrays; use Integer_Arrays; procedure Show_Array_Concatenation is A, B : Integer_Array (1 .. 5); R : Integer_Array (1 .. 10); begin A := (1 & 2 & 3 & 4 & 5); B := (6 & 7 & 8 & 9 & 10); R := A & B; for E of R loop Put (E'Image & ' '); end loop; New_Line; end Show_Array_Concatenation;

In this example, we're using the primitive `&` operator to concatenate the `A` and `B` arrays in the assignment to `R`. Similarly, we're concatenating individual components (integer values) to create an aggregate that we assign to `A` and `B`.

In contrast to this, the addition operator is not available for arrays:

package Integer_Arrays is type Integer_Array is array (Positive range <>) of Integer; end Integer_Arrays;
with Ada.Text_IO; use Ada.Text_IO; with Integer_Arrays; use Integer_Arrays; procedure Show_Array_Addition is A, B, R : Integer_Array (1 .. 5); begin A := (1 & 2 & 3 & 4 & 5); B := (6 & 7 & 8 & 9 & 10); R := A + B; for E of R loop Put (E'Image & ' '); end loop; New_Line; end Show_Array_Addition;

We can, however, define custom operators for any type. For example, if a specific type doesn't have a predefined addition operator, we can define our own `+` operator for it.

Note that we're limited to the operator symbols that are already defined by the Ada language (see the previous table for the complete list of operators). In other words, the operator we define must be selected from one of those existing symbols; we cannot use new symbols for custom operators.

In other languages

Some programming languages — such as Haskell — allow you to define and use custom operator symbols. For example, in Haskell, you can create a new "broken bar" (¦) operator for integer values:

```(¦) :: Int -> Int -> Int
a ¦ b = a + a + b

main = putStrLn \$ show (2 ¦ 3)
```

This is not possible in Ada.

Let's define a custom addition operator that adds individual components of the `Integer_Array` type:

package Integer_Arrays is type Integer_Array is array (Positive range <>) of Integer; function "+" (Left, Right : Integer_Array) return Integer_Array with Post => (for all I in "+"'Result'Range => "+"'Result (I) = Left (I) + Right (I)); end Integer_Arrays;
package body Integer_Arrays is function "+" (Left, Right : Integer_Array) return Integer_Array is R : Integer_Array (Left'Range); begin for I in Left'Range loop R (I) := Left (I) + Right (I); end loop; return R; end "+"; end Integer_Arrays;
with Ada.Text_IO; use Ada.Text_IO; with Integer_Arrays; use Integer_Arrays; procedure Show_Array_Addition is A, B, R : Integer_Array (1 .. 5); begin A := (1 & 2 & 3 & 4 & 5); B := (6 & 7 & 8 & 9 & 10); R := A + B; for E of R loop Put (E'Image & ' '); end loop; New_Line; end Show_Array_Addition;

Now, the `R := A + B` line doesn't trigger a compilation error anymore because the `+` operator is defined for the `Integer_Array` type.

In the implementation of the `+`, we return an array with the range of the `Left` array where each component is the sum of the `Left` and `Right` arrays. In the declaration of the `+` operator, we're defining the expected behavior in the postcondition. Here, we're saying that, for each index of the resulting array (`for all I in "+"'Result'Range`), the value of each component of the resulting array at that specific index is the sum of the components from the `Left` and `Right` arrays at the same index (`"+"'Result (I) = Left (I) + Right (I)`). (`for all` denotes a quantified expression.)

Note that, in this implementation, we assume that the range of `Right` is a subset of the range of `Left`. If that is not the case, the `Constraint_Error` exception will be raised at runtime in the loop. (You can test this by declaring `B` as `Integer_Array (5 .. 10)`, for example.)

We can also define custom operators for record types. For example, we could declare two `+` operators for a record containing the name and address of a person:

package Addresses is type Person is private; function "+" (Name : String; Address : String) return Person; function "+" (Left, Right : Person) return Person; procedure Display (P : Person); private subtype Name_String is String (1 .. 40); subtype Address_String is String (1 .. 100); type Person is record Name : Name_String; Address : Address_String; end record; end Addresses;
with Ada.Strings.Fixed; use Ada.Strings.Fixed; with Ada.Text_IO; use Ada.Text_IO; package body Addresses is function "+" (Name : String; Address : String) return Person is begin return (Name => Head (Name, Name_String'Length), Address => Head (Address, Address_String'Length)); end "+"; function "+" (Left, Right : Person) return Person is begin return (Name => Left.Name, Address => Right.Address); end "+"; procedure Display (P : Person) is begin Put_Line ("Name: " & P.Name); Put_Line ("Address: " & P.Address); New_Line; end Display; end Addresses;
with Ada.Text_IO; use Ada.Text_IO; with Addresses; use Addresses; procedure Show_Address_Addition is John : Person := "John" + "4 Main Street"; Jane : Person := "Jane" + "7 High Street"; begin Display (John); Display (Jane); Put_Line ("----------------"); Jane := Jane + John; Display (Jane); end Show_Address_Addition;

In this example, the first `+` operator takes two strings — with the name and address of a person — and returns an object of `Person` type. We use this operator to initialize the `John` and `Jane` variables.

The second `+` operator in this example brings two people together. Here, the person on the left side of the `+` operator moves to the home of the person on the right side. In this specific case, Jane is moving to John's house.

As a small remark, we usually expect that the `+` operator is commutative. In other words, changing the order of the elements in the operation doesn't change the result. However, in our definition above, this is not the case, as we can confirm by comparing the operation in both orders:

with Ada.Text_IO; use Ada.Text_IO; with Addresses; use Addresses; procedure Show_Address_Addition is John : constant Person := "John" + "4 Main Street"; Jane : constant Person := "Jane" + "7 High Street"; begin if Jane + John = John + Jane then Put_Line ("It's commutative!"); else Put_Line ("It's not commutative!"); end if; end Show_Address_Addition;

In this example, we're using the primitive `=` operator for the `Person` to assess whether the result of the addition is commutative.

In the Ada Reference Manual

## Expression functions

Usually, we implement Ada functions with a construct like this: `begin return X; end;`. In other words, we create a `begin ... end;` block and we have at least one `return` statement in that block. An expression function, in contrast, is a function that is implemented with a simple expression in parentheses, such as `(X);`. In this case, we don't use a `begin ... end;` block or a `return` statement.

As an example of an expression, let's say we want to implement a function named `Is_Zero` that checks if the value of the integer parameter `I` is zero. We can implement this function with the expression `I = 0`. In the usual approach, we would create the implementation by writing `is begin return I = 0; end Is_Zero;`. When using expression functions, however, we can simplify the implementation by just writing `is (I = 0);`. This is the complete code of `Is_Zero` using an expression function:

package Expr_Func is function Is_Zero (I : Integer) return Boolean is (I = 0); end Expr_Func;

An expression function has the same effect as the usual version using a block. In fact, the code above is similar to this implementation of the `Is_Zero` function using a block:

package Expr_Func is function Is_Zero (I : Integer) return Boolean; end Expr_Func;
package body Expr_Func is function Is_Zero (I : Integer) return Boolean is begin return I = 0; end Is_Zero; end Expr_Func;

The only difference between these two versions of the `Expr_Func` packages is that, in the first version, the package specification contains the implementation of the `Is_Zero` function, while, in the second version, the implementation is in the body of the `Expr_Func` package.

An expression function can be, at same time, the specification and the implementation of a function. Therefore, in the first version of the `Expr_Func` package above, we don't have a separate implementation of the `Is_Zero` function because `(I = 0)` is the actual implementation of the function. Note that this is only possible for expression functions; you cannot have a function implemented with a block in a package specification. For example, the following code is wrong and won't compile:

package Expr_Func is function Is_Zero (I : Integer) return Boolean is begin return I = 0; end Is_Zero; end Expr_Func;

We can, of course, separate the function declaration from its implementation as an expression function. For example, we can rewrite the first version of the `Expr_Func` package and move the expression function to the body of the package:

package Expr_Func is function Is_Zero (I : Integer) return Boolean; end Expr_Func;
package body Expr_Func is function Is_Zero (I : Integer) return Boolean is (I = 0); end Expr_Func;

In addition, we can use expression functions in the private part of a package specification. For example, the following code declares the `Is_Valid` function in the specification of the `My_Data` package, while its implementation is an expression function in the private part of the package specification:

package My_Data is type Data is private; function Is_Valid (D : Data) return Boolean; private type Data is record Valid : Boolean; end record; function Is_Valid (D : Data) return Boolean is (D.Valid); end My_Data;

Naturally, we could write the function implementation in the package body instead:

package My_Data is type Data is private; function Is_Valid (D : Data) return Boolean; private type Data is record Valid : Boolean; end record; end My_Data;
package body My_Data is function Is_Valid (D : Data) return Boolean is (D.Valid); end My_Data;

In the Ada Reference Manual

Note

This section was originally written by Robert A. Duff and published as Gem #50: Overload Resolution.

Ada allows overloading of subprograms, which means that two or more subprogram declarations with the same name can be visible at the same place. Here, "name" can refer to operator symbols, like `"+"`. Ada also allows overloading of various other notations, such as literals and aggregates.

In most languages that support overloading, overload resolution is done "bottom up" — that is, information flows from inner constructs to outer constructs. As usual, computer folks draw their trees upside-down, with the root at the top. For example, if we have two procedures `Print`:

procedure Show_Overloading is package Types is type Sequence is null record; type Set is null record; procedure Print (S : Sequence) is null; procedure Print (S : Set) is null; end Types; use Types; X : Sequence; begin -- Compiler selects Print (S : Sequence) Print (X); end Show_Overloading;

the type of `X` determines which `Print` is meant in the call.

Ada is unusual in that it supports top-down overload resolution as well:

procedure Show_Top_Down_Overloading is package Types is type Sequence is null record; type Set is null record; function Empty return Sequence is ((others => <>)); function Empty return Set is ((others => <>)); procedure Print_Sequence (S : Sequence) is null; procedure Print_Set (S : Set) is null; end Types; use Types; X : Sequence; begin -- Compiler selects function Empty return Sequence Print_Sequence (Empty); end Show_Top_Down_Overloading;

The type of the formal parameter `S` of `Print_Sequence` determines which `Empty` is meant in the call. In C++, for example, the equivalent of the `Print (X)` example would resolve, but the `Print_Sequence (Empty)` would be illegal, because C++ does not use top-down information.

If we overload things too heavily, we can cause ambiguities:

procedure Show_Overloading_Error is package Types is type Sequence is null record; type Set is null record; function Empty return Sequence is ((others => <>)); function Empty return Set is ((others => <>)); procedure Print (S : Sequence) is null; procedure Print (S : Set) is null; end Types; use Types; X : Sequence; begin Print (Empty); -- Illegal! end Show_Overloading_Error;

The call is ambiguous, and therefore illegal, because there are two possible meanings. One way to resolve the ambiguity is to use a qualified expression to say which type we mean:

```Print (Sequence'(Empty));
```

Note that we're now using both bottom-up and top-down overload resolution: `Sequence'` determines which `Empty` is meant (top down) and which `Print` is meant (bottom up). You can qualify an expression, even if it is not ambiguous according to Ada rules — you might want to clarify the type because it might be ambiguous for human readers.

Of course, you could instead resolve the `Print (Empty)` example by modifying the source code so the names are unique, as in the earlier examples. That might well be the best solution, assuming you can modify the relevant sources. Too much overloading can be confusing. How much is "too much" is in part a matter of taste.

Ada really needs to have top-down overload resolution, in order to resolve literals. In some languages, you can tell the type of a literal by looking at it, for example appending `L` (letter el) means "the type of this literal is long int". That sort of kludge won't work in Ada, because we have an open-ended set of integer types:

procedure Show_Literal_Resolution is type Apple_Count is range 0 .. 100; procedure Peel (Count : Apple_Count) is null; begin Peel (20); end Show_Literal_Resolution;

You can't tell by looking at the literal `20` what its type is. The type of formal parameter `Count` tells us that `20` is an `Apple_Count`, as opposed to some other type, such as `Standard.Long_Integer`.

Technically, the type of `20` is `universal_integer`, which is implicitly converted to `Apple_Count` — it's really the result type of that implicit conversion that is at issue. But that's an obscure point — you won't go too far wrong if you think of the integer literal notation as being overloaded on all integer types.

Developers sometimes wonder why the compiler can't resolve something that seems obvious. For example:

procedure Show_Literal_Resolution_Error is type Apple_Count is range 0 .. 100; procedure Slice (Count : Apple_Count) is null; type Orange_Count is range 0 .. 10_000; procedure Slice (Count : Orange_Count) is null; begin Slice (Count => (10_000)); -- Illegal! end Show_Literal_Resolution_Error;

This call is ambiguous, and therefore illegal. But why? Clearly the developer must have meant the `Orange_Count` one, because `10_000` is out of range for `Apple_Count`. And all the relevant expressions happen to be static.

Well, a good rule of thumb in language design (for languages with overloading) is that the overload resolution rules should not be "too smart". We want this example to be illegal to avoid confusion on the part of developers reading the code. As usual, a qualified expression fixes it:

```Slice (Count => Orange_Count'(10_000));
```

Another example, similar to the literal, is the aggregate. Ada uses a simple rule: the type of an aggregate is determined top down (i.e., from the context in which the aggregate appears). Bottom-up information is not used; that is, the compiler does not look inside the aggregate in order to determine its type.

procedure Show_Record_Resolution_Error is type Complex is record Re, Im : Float; end record; procedure Grind (X : Complex) is null; procedure Grind (X : String) is null; begin Grind (X => (Re => 1.0, Im => 1.0)); -- Illegal! end Show_Record_Resolution_Error;

There are two `Grind` procedures visible, so the type of the aggregate could be `Complex` or `String`, so it is ambiguous and therefore illegal. The compiler is not required to notice that there is only one type with components `Re` and `Im`, of some real type — in fact, the compiler is not allowed to notice that, for overloading purposes.

We can qualify as usual:

```Grind (X => Complex'(Re => 1.0, Im => 1.0));
```

Only after resolving that the type of the aggregate is `Complex` can the compiler look inside and make sure `Re` and `Im` make sense.

This not-too-smart rule for aggregates helps prevent confusion on the part of developers reading the code. It also simplifies the compiler, and makes the overload resolution algorithm reasonably efficient.

We've seen previously that we can define custom operators for any type. We've also seen that subprograms can be overloaded. Since operators are functions, we're essentially talking about operator overloading, as we're defining the same operator (say `+` or `-`) for different types.

As another example of operator overloading, in the Ada standard library, operators are defined for the `Complex` type of the `Ada.Numerics.Generic_Complex_Types` package. This package contains not only the definition of the `+` operator for two objects of `Complex` type, but also for combination of `Complex` and other types. For instance, we can find these declarations:

```function "+" (Left, Right : Complex) return Complex;
function "+" (Left : Complex;   Right : Real'Base) return Complex;
function "+" (Left : Real'Base; Right : Complex)   return Complex;
```

This example shows that the `+` operator — as well as other operators — are being overloaded in the `Generic_Complex_Types` package.

In the Ada Reference Manual

## Operator Overriding

We can also override operators of derived types. This allows for modifying the behavior of operators for the corresponding derived types.

To override an operator of a derived type, we simply implement a function for that operator. This is the same as how we implement custom operators (as we've seen previously).

As an example, when adding two fixed-point values, the result might be out of range, which causes an exception to be raised. A common strategy to avoid exceptions in this case is to saturate the resulting value. This strategy is typically employed in signal processing algorithms, for example.

In this example, we declare and use the 32-bit fixed-point type `TQ31`:

package Fixed_Point is D : constant := 2.0 ** (-31); type TQ31 is delta D range -1.0 .. 1.0 - D; end Fixed_Point;
with Ada.Text_IO; use Ada.Text_IO; with Fixed_Point; use Fixed_Point; procedure Show_Sat_Op is A, B, C : TQ31; begin A := TQ31'Last; B := TQ31'Last; C := A + B; Put_Line (A'Image & " + " & B'Image & " = " & C'Image); A := TQ31'First; B := TQ31'First; C := A + B; Put_Line (A'Image & " + " & B'Image & " = " & C'Image); end Show_Sat_Op;

Here, we're using the standard `+` operator, which raises a `Constraint_Error` exception in the `C := A + B;` statement due to an overflow. Let's now override the addition operator and enforce saturation when the result is out of range:

package Fixed_Point is D : constant := 2.0 ** (-31); type TQ31 is delta D range -1.0 .. 1.0 - D; function "+" (Left, Right : TQ31) return TQ31; end Fixed_Point;
package body Fixed_Point is function "+" (Left, Right : TQ31) return TQ31 is type TQ31_2 is delta TQ31'Delta range TQ31'First * 2.0 .. TQ31'Last * 2.0; L : constant TQ31_2 := TQ31_2 (Left); R : constant TQ31_2 := TQ31_2 (Right); Res : TQ31_2; begin Res := L + R; if Res > TQ31_2 (TQ31'Last) then return TQ31'Last; elsif Res < TQ31_2 (TQ31'First) then return TQ31'First; else return TQ31 (Res); end if; end "+"; end Fixed_Point;
with Ada.Text_IO; use Ada.Text_IO; with Fixed_Point; use Fixed_Point; procedure Show_Sat_Op is A, B, C : TQ31; begin A := TQ31'Last; B := TQ31'Last; C := A + B; Put_Line (A'Image & " + " & B'Image & " = " & C'Image); A := TQ31'First; B := TQ31'First; C := A + B; Put_Line (A'Image & " + " & B'Image & " = " & C'Image); end Show_Sat_Op;

In the implementation of the overridden `+` operator of the `TQ31` type, we declare another type (`TQ31_2`) with a wider range than `TQ31`. We use variables of the `TQ31_2` type to perform the actual addition, and then we verify whether the result is still in `TQ31`'s range. If it is, we simply convert the result back to the `TQ31` type. Otherwise, we saturate it — using either the first or last value of the `TQ31` type.

When overriding operators, the overridden operator replaces the original one. For example, in the `A + B` operation of the `Show_Sat_Op` procedure above, we're using the overridden version of the `+` operator, which performs saturation. Therefore, this operation doesn't raise an exception (as it was the case with the original `+` operator).

## Nonreturning procedures

Usually, when calling a procedure `P`, we expect that it returns to the caller's thread of control after performing some action in the body of `P`. However, there are situations where a procedure never returns. We can indicate this fact by using the `No_Return` aspect in the subprogram declaration.

A typical example is that of a server that is designed to run forever until the process is killed or the machine where the server runs is switched off. This server can be implemented as an endless loop. For example:

package Servers is procedure Run_Server with No_Return; end Servers;
package body Servers is procedure Run_Server is begin pragma Warnings (Off, "implied return after this statement"); while True loop -- Processing happens here... null; end loop; end Run_Server; end Servers;
with Servers; use Servers; procedure Show_Endless_Loop is begin Run_Server; end Show_Endless_Loop;

In this example, `Run_Server` doesn't exit from the `while True` loop, so it never returns to the `Show_Endless_Loop` procedure.

The same situation happens when we call a procedure that raises an exception unconditionally. In that case, exception handling is triggered, so that the procedure never returns to the caller. An example is that of a logging procedure that writes a message before raising an exception internally:

package Loggers is Logged_Failure : exception; procedure Log_And_Raise (Msg : String) with No_Return; end Loggers;
with Ada.Text_IO; use Ada.Text_IO; package body Loggers is procedure Log_And_Raise (Msg : String) is begin Put_Line (Msg); raise Logged_Failure; end Log_And_Raise; end Loggers;
with Ada.Text_IO; use Ada.Text_IO; with Loggers; use Loggers; procedure Show_No_Return_Exception is Check_Passed : constant Boolean := False; begin if not Check_Passed then Log_And_Raise ("Check failed!"); Put_Line ("This line will not be reached!"); end if; end Show_No_Return_Exception;

In this example, `Log_And_Raise` writes a message to the user and raises the `Logged_Failure`, so it never returns to the `Show_No_Return_Exception` procedure.

We could implement exception handling in the `Show_No_Return_Exception` procedure, so that the `Logged_Failure` exception could be handled there after it's raised in `Log_And_Raise`. However, this wouldn't be considered a normal return to the procedure because it wouldn't return to the point where it should (i.e. to the point where `Put_Line` is about to be called, right after the call to the `Log_And_Raise` procedure).

If a nonreturning procedure returns nevertheless, this is considered a program error, so that the `Program_Error` exception is raised. For example:

package Loggers is Logged_Failure : exception; procedure Log_And_Raise (Msg : String) with No_Return; end Loggers;
with Ada.Text_IO; use Ada.Text_IO; package body Loggers is procedure Log_And_Raise (Msg : String) is begin Put_Line (Msg); end Log_And_Raise; end Loggers;
with Ada.Text_IO; use Ada.Text_IO; with Loggers; use Loggers; procedure Show_No_Return_Exception is Check_Passed : constant Boolean := False; begin if not Check_Passed then Log_And_Raise ("Check failed!"); Put_Line ("This line will not be reached!"); end if; end Show_No_Return_Exception;

Here, `Program_Error` is raised when `Log_And_Raise` returns to the `Show_No_Return_Exception` procedure.

In the Ada Reference Manual

## Inline subprograms

Inlining refers to a kind of optimization where the code of a subprogram is expanded at the point of the call in place of the call itself.

In modern compilers, inlining depends on the optimization level selected by the user. For example, if we select the higher optimization level, the compiler will perform automatic inlining agressively.

In the GNAT toolchain

The highest optimization level (`-O3`) of GNAT performs aggressive automatic inlining. This could mean that this level inlines too much rather than not enough. As a result, the cache may become an issue and the overall performance may be worse than the one we would achieve by compiling the same code with optimization level 2 (`-O2`). Therefore, the general recommendation is to not just select `-O3` for the optimized version of an application, but instead compare it the optimized version built with `-O2`.

It's important to highlight that the inlining we're referring above happens automatically, so the decision about which subprogram is inlined depends entirely on the compiler. However, in some cases, it's better to reduce the optimization level and perform manual inlining instead of automatic inlining. We do that by using the `Inline` aspect.

Let's look at this example:

package Float_Arrays is type Float_Array is array (Positive range <>) of Float; function Average (Data : Float_Array) return Float with Inline; end Float_Arrays;
package body Float_Arrays is function Average (Data : Float_Array) return Float is Total : Float := 0.0; begin for Value of Data loop Total := Total + Value; end loop; return Total / Float (Data'Length); end Average; end Float_Arrays;
with Ada.Text_IO; use Ada.Text_IO; with Float_Arrays; use Float_Arrays; procedure Compute_Average is Values : constant Float_Array := (10.0, 11.0, 12.0, 13.0); Average_Value : Float; begin Average_Value := Average (Values); Put_Line ("Average = " & Float'Image (Average_Value)); end Compute_Average;

When compiling this example, the compiler will most probably inline `Average` in the `Compute_Average` procedure. Note, however, that the `Inline` aspect is just a recommendation to the compiler. Sometimes, the compiler might not be able to follow this recommendation, so it won't inline the subprogram.

These are some examples of situations where the compiler might not be able to inline a subprogram:

• when the code is too large,

• when it's too complicated — for example, when it involves exception handling —, or

• when it contains tasks, etc.

In the GNAT toolchain

In order to effectively use the `Inline` aspect, we need to set the optimization level to at least `-O1` and use the `-gnatn` switch, which instructs the compiler to take the `Inline` aspect into account.

In addition to the `Inline` aspect, in GNAT, we also have the (implementation-defined) `Inline_Always` aspect. In contrast to the former aspect, however, the `Inline_Always` aspect isn't primarily related to performance. Instead, it should be used when the functionality would be incorrect if inlining was not performed by the compiler. Examples of this are procedures that insert Assembly instructions that only make sense when the procedure is inlined, such as memory barriers.

Similar to the `Inline` aspect, there might be situations where a subprogram has the `Inline_Always` aspect, but the compiler is unable to inline it. In this case, we get a compilation error from GNAT.

Note that we can use the `Inline` aspect for generic subprograms as well. When we do this, we indicate to the compiler that we wish it inlines all instances of that generic subprogram.

In the Ada Reference Manual

## Null Procedures

Null procedures are procedures that don't have any effect, as their body is empty. We declare a null procedure by simply writing `is null` in its declaration. For example:

package Null_Procs is procedure Do_Nothing (Msg : String) is null; end Null_Procs;

As expected, calling a null procedure doesn't have any effect. For example:

with Null_Procs; use Null_Procs; procedure Show_Null_Proc is begin Do_Nothing ("Hello"); end Show_Null_Proc;

Null procedures are equivalent to implementing a procedure with a body that only contains `null`. Therefore, the `Do_Nothing` procedure above is equivalent to this:

package Null_Procs is procedure Do_Nothing (Msg : String); end Null_Procs;
package body Null_Procs is procedure Do_Nothing (Msg : String) is begin null; end Do_Nothing; end Null_Procs;

### Null procedures and overriding

We can use null procedures as a way to simulate interfaces for non-tagged types — similar to what actual interfaces do for tagged types. For example, we may start by declaring a type and null procedures that operate on that type. For example, let's model a very simple API:

package Simple_Storage is type Storage_Model is null record; procedure Set (S : in out Storage_Model; V : String) is null; procedure Display (S : Storage_Model) is null; end Simple_Storage;

Here, the API of the `Storage_Model` type consists of the `Set` and `Display` procedures. Naturally, we can use objects of the `Storage_Model` type in an application, but this won't have any effect:

with Ada.Text_IO; use Ada.Text_IO; with Simple_Storage; use Simple_Storage; procedure Show_Null_Proc is S : Storage_Model; begin Put_Line ("Setting 24..."); Set (S, "24"); Display (S); end Show_Null_Proc;

By itself, the `Storage_Model` type is not very useful. However, we can derive other types from it and override the null procedures. Let's say we want to implement the `Integer_Storage` type to store an integer value:

package Simple_Storage is type Storage_Model is null record; procedure Set (S : in out Storage_Model; V : String) is null; procedure Display (S : Storage_Model) is null; type Integer_Storage is private; procedure Set (S : in out Integer_Storage; V : String); procedure Display (S : Integer_Storage); private type Integer_Storage is record V : Integer := 0; end record; end Simple_Storage;
with Ada.Text_IO; use Ada.Text_IO; package body Simple_Storage is procedure Set (S : in out Integer_Storage; V : String) is begin S.V := Integer'Value (V); end Set; procedure Display (S : Integer_Storage) is begin Put_Line ("Value: " & S.V'Image); end Display; end Simple_Storage;
with Ada.Text_IO; use Ada.Text_IO; with Simple_Storage; use Simple_Storage; procedure Show_Null_Proc is S : Integer_Storage; begin Put_Line ("Setting 24..."); Set (S, "24"); Display (S); end Show_Null_Proc;

In this example, we can view `Storage_Model` as a sort of interface for derived non-tagged types, while the derived types — such as `Integer_Storage` — provide the actual implementation.

The section on null records contains an extended example that makes use of null procedures.

In the Ada Reference Manual