User:BenBaron000/sandbox


Note:
At present there is an issue on how transclusions are processed, from Template limits there are several ways to address this limitation but there seems also to be some bugs pending resolution. As is it is impossible to guarantee that all the book's content is displayed in this page. See if you can work with the provided alternatives in the meanwhile or post a request for resolution on at the Wikibooks:Reading room/Technical Assistance.


Ada. Time-tested, safe and secure.


Preface

Welcome to the Ada Programming tutorial at Wikibooks. This is the first Ada tutorial covering the Ada 2005, 2012 and 2022 standards. If you are a beginner you will learn the latest standard — if you are a seasoned Ada user you can see what's new.

Current Development Stage for Ada Programming is "  (June 8, 2024)". At this date, there are more than 600 pages in this book, which makes Ada Programming one of the largest programming wikibooks.[1]

But still there is always room for improvement — do help us to expand Ada Programming. Even beginners will find areas to participate.

About Ada

 
Augusta Ada King, Countess of Lovelace.

Ada is a programming language suitable for all development needs. It has built-in features that directly support structured, object-oriented, generic, distributed and concurrent programming.

Ada is a good choice for Rapid Application Development, Extreme Programming (XP), and Free Software development.

Ada is named after Augusta Ada King-Noel, Countess of Lovelace.

Programming in the large

Ada puts unique emphasis on and provides strong support for, good software engineering practices that scale well to very large software systems (millions of lines of code, and very large development teams). The following language features are particularly relevant in this respect:

  • An extremely strong, static and safe type system, which allows the programmer to construct powerful abstractions that reflect the real world, and allows the compiler to detect many logic faults before they become errors.
  • Modularity, whereby the compiler directly manages the construction of very large software systems from sources.
  • Information hiding; the language separates interfaces from implementation, and provides fine-grained control over visibility.
  • Readability, which helps programmers review and verify code. Ada favours the reader of the program over the writer because a program is written once but read many times. For example, the syntax bans all ambiguous constructs, so there are no surprises, following the Tao of Programming's Law of Least Astonishment. (Some Ada programmers are reluctant to talk about source code which is often cryptic; they prefer program text which is close to English prose.)
  • Portability: the language definition allows compilers to differ only in a few controlled ways, and otherwise defines the semantics of programs very precisely; as a result, Ada source text is very portable across compilers and target hardware platforms. Most often, the program can be recompiled without any changes.[2]
  • Standardisation: standards have been a goal and a prominent feature ever since the design of the language in the late 1970s. The first standard was published in 1980, just 3 years after design commenced. Ada compilers all support the same language; the only dialect, SPARK, is merely an annotated subset and can be compiled with an Ada compiler.

Consequences of these qualities are superior reliability, reusability and maintainability. For example, compared to programs written in C, programs written in Ada 83 contain "70% fewer internal fixes and 90% fewer bugs", and cost half as much to develop in the first place.[3] Ada shines even more in software maintenance, which often accounts for about 80% of the total cost of development. With support for object-oriented programming, Ada 95 may bring even more cost-benefit, depending on how objects are used; although no serious study comparable to Zeigler's has been published.

Programming in the small

In addition to its support for good software engineering practices, which applies to general-purpose programming, Ada has powerful specialised features supporting low-level programming for real-time, safety-critical and embedded systems. Such features include, among others, machine code insertions, address arithmetic, low-level access to memory, control over bitwise representation of data, bit manipulations, and a well-defined, statically provable concurrent computing model called the Ravenscar Profile.

Other features include restrictions (it is possible to restrict which language features are accepted in a program) and features that help review and certify the object code generated by the compiler.

Several vendors provide Ada compilers accompanied by minimal run-time kernels suitable for use in certified, life-critical applications. It is also possible to write Ada programs which require no run-time kernel at all.

It should come as no surprise that Ada is heavily used in the aerospace, defence, medical, railroad, and nuclear industries.

The Language Reference Manual

The Ada Reference Manual (RM) is the official language definition. If you have a problem and no one else can help, you should read the RM (albeit often a bit cryptic for non-language lawyers). For this reason, all complete (not draft) pages in Ada Programming contain links to the appropriate pages in the RM.

This tutorial covers Ada Reference Manual — ISO/IEC 8652:2023 Language and Standard Libraries, colloquially known as Ada 2022 or just Ada.

You can browse the complete Reference Manual at http://www.ada-auth.org/standards/22rm/html/RM-TOC.html

There are two companion documents:

  • The Annotated Reference Manual, an extended version of the RM aimed at compiler writers or other persons who want to know the fine details of the language.
  • The Overview of Ada 2022, an explanation of the features of this language edition.

The Ada Information Clearinghouse also offers the older Ada 83, 95, 2005 and 2012 standards and companion documents.

The RM is a collective work under the control of Ada users. If you think you've found a problem in the RM, please report it to the Ada Conformity Assessment Authority (the Ada RM explains how to do this, see http://www.ada-auth.org/standards/22rm/html/RM-0-2.html Introduction (58/1) ff). On this site, you can also see the list of "Ada Issues" raised by other people.

Ada Conformity Assessment Test Suite

Unlike other programming languages, Ada compilers are officially tested, and only those which pass this test are accepted, for military and commercial work. This means that all Ada compilers behave (almost) the same, so you do not have to learn any dialects. The Ada standard does however allow compiler writers to include additional features and libraries that are not part of the standard.

Programming in Ada

Reading a document online is often quite cumbersome. So if you just started you can also download All Chapters (PDF version)   and print them out.

Getting Started

Where to get a compiler, how to compile the source, all answered here:

Language Features

These chapters look at the broader picture, introducing you to the main Ada features in a tutorial style.

Computer Programming

The following articles are Ada adaptations from articles of the Computer programming book. The texts of these articles are language neutral but the examples are all Ada.

Language Reference

Within the following chapters we look at foundations of Ada. These chapters may be used for reference of a particular keyword, delimiter, operator and so forth.

Predefined Language Libraries

This section is a reference of the Ada Standard Library, which is extensive and well structured. It has these four root packages:

Besides the Standard Library, compilers usually come with a built-in library. This chapter describes the GNAT library in particular.

External Libraries

This section is a reference of third-party Ada libraries which are not part of the compiler predefined environment but are freely available.

External resources

Collections

Printable Versions

The following are collection pages. All collection pages are comprised of groups of the already available pages. You can use them for printing or to gain a quick overview. Please note that those pages are partly very long.

Tutorial
Show HTML (1,839 kb)Download PDF (2,663 kb, 243 pages)
Keywords
Show HTML (470 kb)Download PDF (290 kb, 59 pages)
Operators
Show HTML (232 kb)Download PDF (189 kb, 27 pages)

Source Code

The Source from the Book is available for download and online browsing. The latter allows "drill down", meaning that you can follow the links right down to the package bodies in the Ada runtime library.

References

  1. See Category:Book:Ada Programming or /All Chapters
  2. Gaetan Allaert, Dirk Craeynest, Philippe Waroquiers (2003). "European air traffic flow management: porting a large application to GNU/linux". Proceedings of the 2003 annual ACM SIGAda international conference on Ada. SIGAda'03. pp. 29–37. doi:10.1145/958420.958426. ISBN 1-58113-476-2. http://www.sigada.org/conf/sigada2003/SIGAda2003-CDROM/SIGAda2003-Proceedings/p29-allaert.pdf. Retrieved 2009-01-02.  Paper by Eurocontrol (PDF, 160 kB) on portability.
  3. Stephen F. Zeigler (1995-03-30). "Comparing Development Costs of C and Ada". Retrieved 2009-01-02. Our data indicates that Ada has saved us millions of development dollars. {{cite journal}}: Cite journal requires |journal= (help)

Further reading

Ada 2005 textbooks

Ada 2012 textbooks

Ada 2022 textbooks

Manuals and guides

High-Integrity Software

Authors and contributors

This Wikibook has been written by:

If you wish to contribute as well you should read Contributing and join us at the Contributors lounge.


Basic Ada

"Hello, world!" programs

"Hello, world!"

A common example of a language's syntax is the Hello world program. Here is a straightforward Ada Implementation:

File: hello_world_1.adb, Crate: basic (view, plain text, download with Alire, Alire crate info)
with Ada.Text_IO;

procedure Hello is
begin
   Ada.Text_IO.Put_Line("Hello, world!");
end Hello;

The with statement adds the package Ada.Text_IO to the program. This package comes with every Ada compiler and contains all functionality needed for textual Input/Output. The with statement makes the declarations of Ada.Text_IO available to procedure Hello. This includes the types declared in Ada.Text_IO, the subprograms of Ada.Text_IO and everything else that is declared in Ada.Text_IO for public use. In Ada, packages can be used as toolboxes. Text_IO provides a collection of tools for textual input and output in one easy-to-access module. Here is a partial glimpse at package Ada.Text_IO:

package Ada.Text_IO is

   type File_Type is limited private;

   --  more stuff

   procedure Open(File : in out File_Type;
                  Mode : File_Mode;
                  Name : String;
                  Form : String := "");

   --  more stuff

   procedure Put_Line (Item : String);

   --  more stuff

end Ada.Text_IO;

Next in the program we declare a main procedure. An Ada main procedure does not need to be called "main". Any simple name is fine so here it is Hello. Compilers might allow procedures or functions to be used as main subprograms. [1]

The call on Ada.Text_IO.Put_Line writes the text "Hello World" to the current output file.

A with clause makes the content of a package visible by selection: we need to prefix the procedure name Put_Line from the Text_IO package with its full package name Ada.Text_IO. If you need procedures from a package more often some form of shortcut is needed. There are two options open:

"Hello, world!" with renames

By renaming a package it is possible to give a shorter alias to any package name.[2] This reduces the typing involved while still keeping some of the readability.

File: hello_world_2.adb, Crate: basic (view, plain text, download with Alire, Alire crate info)
with Ada.Text_IO;

procedure Hello is
   package IO renames Ada.Text_IO;
begin
   IO.Put_Line("Hello, world!");
   IO.New_Line;
   IO.Put_Line("I am an Ada program with package rename.");
end Hello;

"Hello, world!" with local use

The use clause makes all the content of a package directly visible for the scope it is declared it. use can be placed locally or globally (see below). Like rename this reduces the typing involved while still keeping some of the readability.

File: hello_world_3.adb, Crate: basic (view, plain text, download with Alire, Alire crate info)
with Ada.Text_IO;

procedure Hello is
   use Ada.Text_IO;   
begin
   Put_Line("Hello, world!");
   New_Line;
   Put_Line("I am an Ada program with package use.");
end Hello;

use can be used for packages and in the form of use type for types. use type makes only the operators of the given type directly visible but not any other operations on the type.

"Hello, world!" with global use

Using use clause outside any scope will makes all the content of a package directly visible for the whole compilation unit. It allows even less typing but removes more of the readability and can lead to name clashes.

File: hello_world_4.adb, Crate: basic (view, plain text, download with Alire, Alire crate info)
with Ada.Text_IO;
use Ada.Text_IO;

procedure Hello is    
begin
   Put_Line("Hello, world!");
   New_Line;
   Put_Line("I am an Ada program with package use.");
end Hello;

With that many options one need to consider which option to use when. One suggested "rule of thumb":

  • global use for the most used package(s)
  • renames for most other package(s)
  • local use for packages only used in a single procedure
  • no use use or renames for package(s) only used once

You might have another simpler rule (for example, always use package Ada and its children, never use anything else).

Another rule from the early days of Ada development was global use for all packages unless a name clash occurs.

Compiling the "Hello, world!" program

For information on how to build the "Hello, world!" program on various compilers, see the Building chapter.

FAQ: Why is "Hello, world!" so big?

Ada beginners frequently ask how it can be that such a simple program as "Hello, world!" results in such a large executable. The reason has nothing to do with Ada but can usually be found in the compiler and linker options used — or better, not used.

Standard behavior for Ada compilers — or good compilers in general — is not to create the best code possible but to be optimized for ease of use. This is done to ensure a system that works "out of the box" and thus does not frighten away potential new users with unneeded complexity.

The GNAT project files, which you can download alongside the example programs, use better tuned compiler, binder and linker options. If you use those your "Hello, world!" will be a lot smaller:

 32K ./Linux-i686-Debug/hello_world_1
8.0K ./Linux-i686-Release/hello_world_1
 36K ./Linux-x86_64-Debug/hello_world_1
 12K ./Linux-x86_64-Release/hello_world_1
1.1M ./Windows_NT-i686-Debug/hello_world_1.exe
 16K ./Windows_NT-i686-Release/hello_world_1.exe
 32K ./VMS-AXP-Debug/hello_world_1.exe
 12K ./VMS-AXP-Release/hello_world_1.exe

For comparison the sizes for a plain gnat make compile:

497K hello_world_1 (Linux i686)
500K hello_world_1 (Linux x86_64)
1.5M hello_world_1.exe (Windows_NT i686)
589K hello_world_1.exe (VMS AXP)

Worth mentioning is that hello_world (Ada, C, C++) compiled with GNAT/MSVC 7.1/GCC(C) all produces executables with approximately the same size given comparable optimisation and linker methods.

Things to look out for

It will help to be prepared to spot a number of significant features of Ada that are important for learning its syntax and semantics.

Comb Format

There is a comb format in all the control structures and module structures. See the following examples for the comb format. You don't have to understand what the examples do yet - just look for the similarities in layout.

if Boolean expression then
   statements
elsif Boolean expression then
   statements
else
   statements
end if;
while Boolean expression loop
   statements
end loop;
for variable in range loop
   statements
end loop;
declare
   declarations
begin
   statements
exception
   handlers
end;
procedure P (parameters : in out type) is
   declarations
begin
   statements
exception
   handlers
end P;
function F (parameters : in type) return type is
   declarations
begin
   statements
exception
   handlers
end F;
package P is
   declarations
private
   declarations
end P;
generic
   declarations
package P is
   declarations
private
   declarations
end P;
generic
   declarations
procedure P (parameters : in out type);

Note that semicolons consistently terminate statements and declarations; the empty line (or a semicolon alone) is not a valid statement: the null statement is

null;

Type and subtype

There is an important distinction between type and subtype: a type is given by a set of values and their operations. A subtype is given by a type, and a constraint that limits the set of values. Values are always of a type. Objects (constants and variables) are of a subtype. This generalizes, clarifies and systematizes a relationship, e.g. between Integer and 1..100, that is handled ad hoc in the semantics of Pascal.

Constrained types and unconstrained types

There is an important distinction between constrained types and unconstrained types. An unconstrained type has one or more free parameters that affect its size or shape. A constrained type fixes the values of these parameters and so determines its size and shape. Loosely speaking, objects must be of a constrained type, but formal parameters may be of an unconstrained type (they adopt the constraint of any corresponding actual parameter). This solves the problem of array parameters in Pascal (among other things).

Dynamic types

Where values in Pascal or C must be static (e.g. the subscript bounds of an array) they may be dynamic in Ada. However, static expressions are required in certain cases where dynamic evaluation would not permit a reasonable implementation (e.g. in setting the number of digits of precision of a floating point type).

Separation of concerns

Ada consistently supports a separation of interface and mechanism. You can see this in the format of a package, which separates its declaration from its body; and in the concept of a private type, whose representation in terms of Ada data structures is inaccessible outside the scope containing its definition.

Where to ask for help

Most Ada experts lurk on the Usenet newsgroups comp.lang.ada (English) and fr.comp.lang.ada (French); they are accessible either with a newsreader or through one of the many web interfaces. This is the place for all questions related to Ada.

People on these newsgroups are willing to help but will not do students' homework for them; they will not post complete answers to assignments. Instead, they will provide guidance for students to find their own answers.

For more online resources, see the External links section in this wikibook's introduction.

Notes

  1. Main subprograms may even have parameters; it is implementation-defined what kinds of subprograms can be used as main subprograms. The reference manual explains the details in 10.2: LRM 10.2(29) [Annotated]: “…, an implementation is required to support all main subprograms that are public parameterless library procedures.” Library means not nested in another subprogram, for example, and other things that needn't concern us now.
  2. renames can also be used for procedures, functions, variables, array elements. It can not be used for types — a type rename can be accomplished with subtype.


Installing

Ada compilers are available from several vendors, on a variety of host and target platforms. The Ada Resource Association maintains a list of available compilers.

Below is an alphabetical list of available compilers with additional comments.

AdaMagic from SofCheck

SofCheck used to produce an Ada 95 front-end that can be plugged into a code generating back-end to produce a full compiler. This front-end is offered for licensing to compiler vendors.

Based on this front-end, SofCheck used to offer:

  • AdaMagic, an Ada-to-C/C++ translator
  • AppletMagic, an Ada-to-Java bytecode compiler

SofCheck has merged with AdaCore under the AdaCore name, leaving no visible trace of AdaMagic offering on AdaCore website.

However, MapuSoft is now licensed to resell AdaMagic. They renamed it to "Ada-to-C/C++ changer". New name sounds like fake. Almost no Ada developer heard of MapuSoft. MapuSoft is never seen making Ada libraries, commercial or FLOSS. They are never seen at Ada conferences. Yet this is a real stuff, a validated Ada compiler that knows lots of tricks required to work on top of C/C++ compilers. E.g. it contains a proven knowledge of handling integer overflow with a special "-1" case.

Thanks to MapuSoft, AdaMagic really became available to developers. Get AppCOE, but not Win64 one, install it. In the MapuSoft/AppCOE_x32/Tools/Ada there will be AdaMagic. AdaMagic is known to support Win64, but AppCOE for Win64 is known to contain no AdaMagic at all.

Using AdaMagic from command line is badly supported in AppCOE, but possible. Set up ADA_MAGIC environment variable, edit Tools/Ada/{linux|windows}/SITE/rts_path to point to real path, edit SITE/config to get rid of unsupported C compiler keys, and compile via e.g.

adareg -key=`test_key | sed -e '/md5/!d;s/md5 = //'` Hello_World.adb
adabgen -key=`test_key | sed -e '/md5/!d;s/md5 = //'` Hello_World

Commercial; proprietary.

AdaMULTI from Green Hills Software

Green Hills Software sells development environments for multiple languages and multiple targets (including DSPs), primarily to embedded software developers.

Languages supported Ada 83, Ada 95, C, C++, Fortran
License for the run-time library Proprietary, royalty free.
Native platforms GNU/Linux on i386, Microsoft Windows on i386, and Solaris on SPARC
Cross platforms INTEGRITY, INTEGRITY-178B and velOSity from Green Hills; VxWorks from Wind River; several bare board targets, including x86, PowerPC, ARM, MIPS and ColdFire/68k. Safety-critical GMART and GSTART run-time libraries certified to DO-178B level A.
Available from http://www.ghs.com/
Support Commercial
Add-ons included IDE, debugger, TimeMachine, integration with various version control systems, source browsers, other utilities

GHS claims to make great efforts to ensure that their compilers produce the most efficient code and often cites the EEMBC benchmark results as evidence, since many of the results published by chip manufacturers use GHS compilers to show their silicon in the best light, although these benchmarks are not Ada specific.

GHS has no publicly announced plans to support the two most recent Ada standards (2005 and 2012) but they do continue to actively market and develop their existing Ada products.

DEC Ada from HP

DEC Ada was an Ada 83 compiler for OpenVMS. While “DEC Ada” is probably the name most users know, the compiler has also been called “HP Ada”, "VAX Ada", and "Compaq Ada".

GNAT, the GNU Ada Compiler from AdaCore and the Free Software Foundation

GNAT is the free GNU Ada compiler, which is part of the GNU Compiler Collection. It is the only Ada compiler that supports all of the optional annexes of the language standard. The original authors formed the company AdaCore to offer professional support, consulting, training and custom development services. It is thus possible to obtain GNAT from many different sources, detailed below.

GNAT is always licensed under the terms of the GNU General Public License.

However, the run-time library uses either the GPL, or the GNAT Modified GPL, depending on where you obtain it.

Several optional add-ons are available from various places:

  • ASIS, the Ada Semantic Interface Specification, is a library that allows Ada programs to examine and manipulate other Ada programs.
  • FLORIST is a library that provides a POSIX programming interface to the operating system.
  • GDB, the GNU Debugger, with Ada extensions.
  • GLADE implements Annex E, the Distributed Systems Annex. With it, one can write distributed programs in Ada, where partitions of the program running on different computers communicate over the network with one another and with shared objects.
  • GPS, the GNAT Programming Studio, is a full-featured integrated development environment, written in Ada. It allows you to code in Ada, C and C++.

Many Free Software libraries are also available.

GNAT GPL (or Community) Edition

As of May 2022, AdaCore no longer supports GNAT GPL. The recommended way to install all the tools and libraries that the Community Edition included is to use Alire, a package manager for Ada sources, which also provides toolchains. Although you can still download and install the last GNAT Community Edition that was published, there won't be any further release.

GNAT Community Edition is a source and binary release from AdaCore, intended for use by Free Software developers only. If you want to distribute your binary programs linked with the GPL run-time library, then you must do so under terms compatible with the GNU General Public License.

As of GNAT GPL Edition 2013:

Languages supported Ada 83, Ada 95, Ada 2005, Ada 2012, C, C++
License for the run-time library pure GPL
Native platforms GNU/Linux on x86_64; Microsoft Windows on i386; ; Mac OS X (Darwin, x86_64). Earlier releases have supported Solaris on SPARC, GNU/Linux on i386, Microsoft .NET on i386
Cross platforms AVR, hosted on Windows; Java VM, hosted on Windows; Mindstorms NXT, hosted on Windows; ARM, hosted on Windows and Linux;
Compiler back-end GCC 4.9
Available from https://www.adacore.com/download
Support None
Add-ons included GDB, GPS in source and binary form; many more in source-only form.

GNAT Modified GPL releases

With these releases of GNAT, you can distribute your programs in binary form under licensing terms of your own choosing; you are not bound by the GPL.

GNAT 3.15p

This is the last public release of GNAT from AdaCore that uses the GNAT Modified General Public License.

GNAT 3.15p has passed the Ada Conformity Assessment Test Suite (ACATS). It was released in October 2002.

The binary distribution from AdaCore also contains an Ada-aware version of the GNU Debugger (GDB), and a graphical front-end to GDB called the GNU Visual Debugger (GVD).

Languages supported Ada 83, Ada 95, C
License for the run-time library GNAT-modified GPL
Native platforms GNU/Linux on i386 (with glibc 2.1 or later), Microsoft Windows on i386, OS/2 2.0 or later on i386, Solaris 2.5 or later on SPARC
Cross platforms none
Compiler back-end GCC 2.8.1
Available from ftp://ftp.cs.kuleuven.ac.be/pub/Ada-Belgium/mirrors/gnu-ada/3.15p/
Support None
Add-ons included ASIS, Florist, GLADE, GDB, Gnatwin (on Windows only), GtkAda 1.2, GVD

GNAT Pro

GNAT Pro is the professional version of GNAT, offered as a subscription package by AdaCore. The package also includes professional consulting, training and maintenance services. AdaCore can provide custom versions of the compiler for native or cross development. For more information, see http://www.adacore.com/.

Languages supported Ada 83, Ada 95, Ada 2005, Ada 2012, C, and optionally C++
License for the run-time library GNAT-modified GPL
Native platforms many, see http://www.adacore.com/home/products/gnatpro/supported_platforms/
Cross platforms many, see http://www.adacore.com/home/products/gnatpro/supported_platforms/; even more on request
Compiler back-end GCC 4.3
Available from http://www.adacore.com/ by subscription (commercial)
Support Commercial; customer-only bug database
Add-ons included ASIS, Florist, GDB, GLADE, GPS, GtkAda, XML/Ada, and many more in source and, on request, binary form.

GCC

GNAT has been part of the Free Software Foundation's GCC since October 2001. The Free Software Foundation does not distribute binaries, only sources. Its licensing of the run-time library for Ada (and other languages) allows the development of proprietary software without necessarily imposing the terms of the GPL.

Most GNU/Linux distributions and several distributions for other platforms include prebuilt binaries; see below.

For technical reasons, we recommend against using the Ada compilers included in GCC 3.1, 3.2, 3.3 and 4.0. Instead, we recommend using GCC 3.4, 4.1 or later, or one of the releases from AdaCore (3.15p, GPL Edition or Pro).

Since October 2003, AdaCore merge most of their changes from GNAT Pro into GCC during Stage 1; this happens once for each major release. Since GCC 3.4, AdaCore has gradually added support for revised language standards, first Ada 2005 and now Ada 2012.

GCC version 4.4 switched to version 3 of the GNU General Public License and grants a Runtime Library Exception similar in spirit to the GNAT Modified General Public License used in all previous versions. This Runtime Library Exception applies to run-time libraries for all languages, not just Ada.

As of GCC 4.7, released on 2012-03-22:

Languages supported Ada 83, Ada 95, Ada 2005, parts of Ada 2012, C, C++, Fortran 95, Java, Objective-C, Objective-C++ (and others)
License for the run-time library GPL version 3 with Runtime Library Exception
Native platforms none (source only)
Cross platforms none (source only)
Compiler back-end GCC 4.7
Available from http://gcc.gnu.org/ in source only form.
Support Volunteer; public bug database
Add-ons included none

The GNU Ada Project

The GNU Ada Project provides source and binary packages of various GNAT versions for several operating systems, and, importantly, the scripts used to create the packages. This may be helpful if you plan to port the compiler to another platform or create a cross-compiler; there are instructions for building your own GNAT compiler for GNU/Linux and Mac OS X users.

Both GPL and GMGPL or GCC Runtime Library Exception versions of GNAT are available.

Languages supported Ada 83, Ada 95, Ada 2005, C. (Some distributions also support Ada 2012, Fortran 90, Java, Objective C and Objective C++)
License for the run-time library pure, GNAT-modified GPL, or GCC Runtime Library Exception
Native platforms Fedora Core 4 and 5, MS-DOS, OS/2, Solaris 10, SuSE 10, Mac OS X, (more?)
Cross platforms none
Compiler back-end GCC 2.8.1, 3.4, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6 (various binary packages)
Available from Sourceforge
Support Volunteer; public bug database
Add-ons included AdaBrowse, ASIS, Booch Components, Charles, GPS, GtkAda (more?)

A# (A-Sharp, a.k.a. Ada for .NET)

This compiler is historical as it has now been merged into GNAT GPL Edition and GNAT Pro.

A# is a port of Ada to the .NET Platform. A# was originally developed at the Department of Computer Science at the United States Air Force Academy which distribute A# as a service to the Ada community under the terms of the GNU general public license. A# integrates well with Microsoft Visual Studio 2005, AdaGIDE and the RAPID open-source GUI Design tool. As of 2006-06-06:

Languages supported Ada 83, Ada 95, C
License for the run-time library pure GPL
Native platforms Microsoft .NET
Cross platforms none
Compiler back-end GCC 3.4 (GNAT GPL 2006 Edition?)
Available from http://sourceforge.net/projects/asharp/
Support None (but see GNAT Pro)
Add-ons included none.

GNAT for AVR microcontrollers

Rolf Ebert and others provide a version of GNAT configured as a cross-compiler to various AVR microcontrollers, as well as an experimental Ada run-time library suitable for use on the microcontrollers. As of Version 1.1.0 (2010-02-25):

Languages supported Ada 83, Ada 95, Ada 2005, C
License for the run-time library GNAT-Modified GPL
Host platforms GNU/Linux and Microsoft Windows on i386
Target platforms Various AVR 8-bit microcontrollers
Compiler back-end GCC 4.7
Available from http://avr-ada.sourceforge.net/
Support Volunteer; public bug database
Add-ons included partial Ada run time system, AVR peripherals support library

GNAT for LEON

The Real-Time Research Group of the Technical University of Madrid (UPM, Universidad Politécnica de Madrid) wrote a Ravenscar-compliant real-time kernel for execution on LEON processors and a modified run-time library. They also provide a GNAT cross-compiler. As of version 2.0.1:

Languages supported Ada 83, Ada 95, Ada 2005, C
License for the run-time library pure GPL
Native platforms none
Cross platforms GNU/Linux on i686 to LEON2 bare boards
Compiler back-end GCC 4.1 (GNAT GPL 2007 Edition)
Available from http://www.dit.upm.es/ork/
Support ?
Add-ons included OpenRavenscar real-time kernel; minimal run-time library

GNAT for Macintosh (Mac OS X)

GNAT for Macintosh provides both FSF (GMGPL) and AdaCore (GPL) versions of GNAT with Xcode and Carbon integration and bindings.

Note that this site was last updated for GCC 4.3 and Mac OS X Leopard (both PowerPC and Intel-based). Aside from the work on integration with Apple’s Carbon graphical user interface and with Xcode 3.1 it may be preferable to see above.

There is also support at MacPorts; the last update (at 25 Nov 2011) was for GCC 4.4.2.

Prebuilt packages as part of larger distributions

Many distributions contain prebuilt binaries of GCC or various public releases of GNAT from AdaCore. Quality varies widely between distributions. The list of distributions below is in alphabetical oder. (Please keep it that way.)

AIDE (for Microsoft Windows)

AIDE — Ada Instant Development Environment is a complete one-click, just-works Ada distribution for Windows, consisting of GNAT, comprehensive documentation, tools and libraries. All are precompiled, and source code is also available. The installation procedure is particularly easy (just unzip to default c:\aide and run). AIDE is intended for beginners and teachers, but can also be used by advanced users.

Languages supported Ada 83, Ada 95, C
License for the run-time library GNAT-modified GPL
Native platforms Microsoft Windows on i386
Cross platforms none
Compiler back-end GCC 2.8.1
Available from https://stef.genesix.org/aide/aide.html
Support stef@genesix.org
Add-ons included ASIS, GDB, GPS, GtkAda, Texinfo (more?)

Cygwin (for Microsoft Windows)

Cygwin, the Linux-like environment for Windows, also contains a version of the GNAT compiler. The Cygwin version of GNAT is older than the MinGW version and does not support DLLs and Multi-Threading (as of 11.2004).

Debian (GNU/Linux and GNU/kFreeBSD)

There is a Debian Policy for Ada which tries to make Debian the best Ada development and deployment platform. The development platform includes the compiler and many libraries, pre-packaged and integrated so as to be easy to use in any program. The deployment platform is the renowned stable distribution, which is suitable for mission-critical workloads and enjoys long life cycles, typically 3 to 4 years. Because Debian is a binary distribution, it is possible to deploy non-free, binary-only programs on it while enjoying all the benefits of a stable platform. Compiler choices are conservative for this reason, and the Policy mandates that all Ada programs and libraries be compiled with the same version of GNAT. This makes it possible to use all libraries in the same program. Debian separates run-time libraries from development packages, so that end users do not have to install the development system just to run a program.

The GNU Ada compiler can be installed on a Debian system with this command:

aptitude install gnat

This will also give you a list of related packages, which are likely to be useful for an Ada programmer.

Debian is unique in that it also allows programmers to use some of GNAT's internal components by means of two libraries:

  • libgnatvsn (licensed under GNAT-Modified GPL) and
  • libgnatprj (the project manager, licensed under pure GPL).

Debian packages make use of these libraries.

In the table below, the information about the future Debian 8.0 Jessie is accurate as of October 2014 and will change.

  3.1 Sarge 4.0 Etch 5.0 Lenny 6.0 Squeeze 7.0 Wheezy 8.0 Jessie
Release date June 2005 April 2007 February 2009 February 2011 May 2013 April 2015
Languages supported Ada 83, Ada 95, C +Ada 2005, parts of Ada 2012, C, C++, Fortran 95, Java, Objective-C, Objective-C++ +Ada 2012
License for the run-time library GNAT-modified GPL (both ZCX and SJLJ versions starting from 5.0 Lenny) GPL version 3 with Run-time library exception
Native platforms: 3.1 Sarge 4.0 Etch 5.0 Lenny 6.0 Squeeze 7.0 Wheezy 8.0 Jessie
alpha yes yes
amd64 yes yes yes yes yes
armel preliminary yes yes
armhf yes yes
hppa yes yes yes
hurd-i386 yes yes
i386 yes yes yes yes yes yes
ia64 yes yes yes yes
kfreebsd-amd64 yes yes yes
kfreebsd-i386 yes yes yes yes yes
mips yes yes yes yes yes
mipsel yes yes yes yes yes
powerpc yes yes yes yes yes yes
ppc64 yes yes yes yes
s390 yes yes yes yes s390x
sparc yes yes yes yes yes yes
Cross platforms none
Compiler back-end GCC 2.8.1 GCC 4.1 GCC 4.3 GCC 4.4 GCC 4.6 GCC 4.9
Available from http://www.debian.org/
Support Volunteer; public bug database; paid support available from third parties; public mailing list
Add-ons included 3.1 Sarge 4.0 Etch 5.0 Lenny 6.0 Squeeze 7.0 Wheezy 8.0 Jessie
ada-reference-manual 1995 1995 1995 2005 2012 2012
AdaBindX 0.7.2
AdaBrowse 4.0.2 4.0.2 4.0.2 4.0.3 4.0.3 -
AdaCGI 1.6 1.6 1.6 1.6 1.6 1.6
AdaControl 1.6r8 1.9r4 1.12r3 1.12r3 1.16r11
APQ (with PostgreSQL) 3.0 3.2 3.2
AdaSockets 1.8.4.7 1.8.4.7 1.8.4.7 1.8.8 1.8.10 1.8.11
Ahven 1.2 1.7 2.1 2.4
Alog 0.1 0.3 0.4.1 -
anet 0.1 0.3.1
ASIS 3.15p 2005 2007 2008 2010 2014
AUnit 1.01 1.03 1.03 1.03 1.03 3.7.1
AWS 2.0 2.2 2.5 prerelease 2.7 2.10.2 3.2.0
Charles 2005-02-17 (superseded by Ada.Containers in gnat)
Florist 3.15p 2006 2006 2009 2011 2014
GDB 5.3 6.4 6.8 7.0.1 7.4.1 7.7.1
GLADE 3.15p 2006 (superseded by PolyORB)
GMPAda 0.0.20091124 0.0.20120331 0.0.20131223
GNADE 1.5.1 1.6.1 1.6.1 1.6.2 1.6.2 -
GNAT Checker 1999-05-19 (superseded by AdaControl)
GPRBuild 1.3.0w 2011 2014
GPS 2.1 4.0.1 4.0.1 4.3 5.0 5.3
GtkAda 2.4 2.8.1 2.8.1 2.14.2 2.24.1 2.24.4
Log4Ada 1.0 1.2 1.2
Narval 1.10.2
OpenToken 3.0b 3.0b 3.0b 4.0b 4.0b 5.0a
PC/SC Ada 0.6 0.7.1 0.7.2
PolyORB 2.6 prerelease 2.8 prerelease 2.11 prerelease
PLPlot 5.9.0 5.9.5 5.9.5 5.10.0
Templates Parser 10.0+20060522 11.1 11.5 11.6 11.8
TextTools 2.0.3 2.0.3 2.0.5 2.0.6 2.1.0
XML/Ada 1.0 2.2 3.0 3.2 4.1 4.4
XML-EZ-out 1.06 1.06.1 1.06.1

The ADT plugin for Eclipse (see section ObjectAda from Aonix) can be used with GNAT as packaged for Debian Etch. Specify "/usr" as the toolchain path.

DJGPP (for MS-DOS)

DJGPP has GNAT as part of their GCC distribution.

DJGPP is a port of a comprehensive collection of GNU utilities to MS-DOS with 32-bit extensions, and is actively supported (as of 1.2005). It includes the whole GCC compiler collection, that now includes Ada. See the DJGPP website for installation instructions.

DJGPP programs run also in a DOS command box in Windows, as well as in native MS-DOS systems.

FreeBSD and DragonFly

FreeBSD's ports collection has an Ada framework with an expanding set of software packages. The Framework is currently built by FSF GCC 6.3.1, although FSF GCC 5.4 can optionally be used instead. The AdaCore GPL compilers are not present. There are several reasons for this, not the least of which is the addition maintenance of multiple compilers is significant. There are no non-GCC based Ada compilers represented in ports either.

While FreeBSD does have a snapshot that goes with each release, the ports are updating in a rolling fashion continuously, and the vast majority of users prefer the "head" of ports which has the latest packages.

Languages supported Ada 83, Ada 95, Ada 2005, Ada 2012, C, C++, ObjC, Fortran
License for the run-time library GPLv3 with Runtime Library Exception v3
Native platforms FreeBSD i386, FreeBSD AMD64, FreeBSD ARM64, DragonFly x86-64
Cross platforms FreeBSD/DragonFly->Android (targets ARMv7 and x86), FreeBSD/DragonFly->FreeBSD/ARM64 (targets Aarch64)
Compiler back-end GCC 6.3.1
Available from http://www.freebsd.org, https://github.com/DragonFlyBSD/DPorts
Support Volunteer; public bug database

There are two ways to install the software. The quickest and easiest way is to install prebuilt binaries using the command "pkg install <pkg name>". For example, to install the GNAT Programming Studio and all of its dependencies including the GNAT compiler, all you need is one command:

pkg install gps-ide

If a specific package is not available, or the user just prefers to build from source (this can take a long time), then a typical command would be:

cd /usr/ports/devel/gps && make install clean

As with the binary installation, if any dependencies are missing they will be built first, also from source.

Available software as of 8 February 2017

Directory Common Name version pkg name
archivers/zip-ada Zip-Ada (Library) 52 zip-ada
cad/ghdl GNU VHDL simulator 0.33 ghdl
databases/adabase Thick bindings to Postgres, MySQL and SQLite 3.0 adabase
databases/apq Ada95 database interface library 3.2.0 apq
databases/apq-mysql APQ MySQL driver 3.2.0 apq-mysql
databases/apq-odbc APQ ODBC driver 3.2.0 apq-odbc
databases/apq-pgsql APQ PostgreSQL driver 3.2.0 apq-pgsql
devel/ada-util Ada 2005 app utilities (Library) 1.8.0 ada-util
devel/adaid UUID generation library 0.0.1 adaid
devel/adabooch Ada95 Booch Components (Library) 2016-03-21 adabooch
devel/adacurses AdaCurses (Binding) 2015-08-08 adacurses
devel/afay AFlex and AYACC parser generators 041111 afay
devel/ahven Ahven (Unit Test Library) 2.6 ahven
devel/alog Stackable logging framework 0.5.2 alog
devel/aunit Unit testing framework 2016 aunit
devel/florist-gpl Florist (Posix Binding) 2016 florist-gpl
devel/gnatcoll GNAT Component Collection 2016 gnatcoll
devel/gnatpython GNATPython (python-based test framework) 2014-02-24 gnatpython
devel/gprbuild GPRbuild (Multi-language build tool) 20160609 gprbuild
devel/gps GNAT Programming Studio 2016 gps-ide
devel/libspark2012 SPARK 2012 library source files 2012 libspark2012
devel/matreshka Matreshka (Info Systems Library) 0.7.0 matreshka
devel/pcsc-ada PCSC library 0.7.3 pcsc-ada
devel/pragmarcs PragmAda Reusable Components 20161207 pragmarcs
devel/sdl_gnat GNAT SDL bindings (Thin) 2013 sdl_gnat
devel/simple_components Simple Ada components 4.18 simple_components
dns/ironsides Spark/Ada Ironsides DNS Server 2015-04-15 ironsides
graphics/generic_image_decoder image decoder library 05 generic_image_decoder
lang/adacontrol AdaControl (Construct detection tool) 1.17r3 adacontrol
lang/asis Ada Semantic Interface Specification 2016 asis
lang/gcc5-aux GNAT Ada compiler (FSF GCC) 5.4 (2016-06-03) gcc5-aux
lang/gcc6-aux GNAT Ada compiler (FSF GCC) 6.3.1 (2017-02-02) gcc6-aux
lang/gnat_util GNAT sources (helper Library) 2017-02-02 gnat_util
lang/gnatcross-aarch64 FreeBSD/ARM64 cross-compiler, Aarch64 2017-02-02 (6.3.1) gnatcross-aarch64
lang/gnatcross-binutils-aarch64 GNU Binutils used by FreeBSD/ARM64 cross-compiler 2.27 gnatcross-binutils-aarch64
lang/gnatcross-sysroot-aarch64 FreeBSD/ARM64 sysroot 1 gnatcross-sysroot-aarch64
lang/gnatdroid-armv7 Android 5.0 cross-compiler, ARMv7 2017-02-02 (6.3.1) gnatdroid-armv7
lang/gnatdroid-binutils GNU Binutils used by Android cross-compiler 2.27 gnatdroid-binutils
lang/gnatdroid-binutils-x86 GNU Binutils used by Android cross-compiler (x86) 2.27 gnatdroid-binutils-x86
lang/gnatdroid-sysroot Android API 4.0 to 6.0 sysroot 23 gnatdroid-sysroot
lang/gnatdroid-sysroot-x86 Android API 4.4 to 6.0 sysroot (x86) 23 gnatdroid-sysroot-x86
lang/gnatdroid-x86 Android 5.0 cross-compiler, x86 2017-02-02 (6.3.1) gnatdroid-x86
lang/lua-ada Ada bindings for Lua 1.0 ada-lua
math/plplot-ada PLplot Ada bindings 5.12.0 plplot-ada
misc/excel-writer Excel output library 15 excel-writer
misc/ini_file_manager Configuration file library 03 ini_file_manager
net/adasockets IPv4 socket library 1.10 adasockets
net/anet Network library (IPv4 and IPv6) 0.3.4 anet
net/polyorb PolyORB (CORBA/SOAP/DSA middleware) 2.11.1 (2014) polyorb
security/libadacrypt Cryptography Library (symm & asymm) 20151019 libadacrypt
security/libsparkcrypto LibSparkCrypto (Cryptography Library) 0.1.1 libsparkcrypto
shells/sparforte Shell and scripting language for mission-critical projects 2.0.2 spareforte
textproc/adabrowse AdaBrowse (Ada95 HTML doc. generator) 4.0.3 adabrowse
textproc/opentoken Ada Lex analyzer and parser 6.0b opentoken
textproc/py-sphinxcontrib-adadomain Sphinx documentation generator for Ada 0.1 py27-sphinxcontrib-adadomain
textproc/templates_parser AWS Template Parser library 17.0.0 templates_parser
textproc/words Words (Latin/English dictionary) 1.97F words
textproc/xml_ez_out XML output (Library) 1.06 xml_ez_out
textproc/xmlada XML/Ada (Library) 17.0.0 xmlada
www/aws Ada Web Server 17.0.1 aws
www/aws-demos Ada Web Server demos 17.0.1 aws-demos
x11-toolkits/gtkada GTK2/Ada (bindings) 2.24.4 gtkada
x11-toolkits/gtkada3 GTK3/Ada (bindings) 3.14.2 gtkada3

Gentoo GNU/Linux

The GNU Ada compiler can be installed on a Gentoo system using emerge:

 emerge dev-lang/gnat

In contrast to Debian, Gentoo is primarily a source distribution, so many packages are available only in source form, and require the user to recompile them (using emerge).

Also in contrast to Debian, Gentoo supports several versions of GNAT in parallel on the same system. Be careful, because not all add-ons and libraries are available with all versions of GNAT.

Languages supported Ada 83, Ada 95, Ada 2005, C (more?)
License for the run-time library pure or GNAT-modified GPL (both available)
Native platforms Gentoo GNU/Linux on amd64, powerpc and i386
Cross platforms none
Compiler back-end GCC 3.4, 4.1 (various binary packages)
Available from http://www.gentoo.org/ (see other Gentoo dev-ada packages)
Support Volunteer; public bug database
Add-ons included AdaBindX, AdaBroker, AdaDoc, AdaOpenGL, AdaSockets, ASIS, AUnit, Booch Components, CBind, Charles, Florist, GLADE, GPS, GtkAda, XML/Ada

Mandriva Linux

The GNU Ada compiler can be installed on a Mandriva system with this command:

urpmi gnat

MinGW (for Microsoft Windows)

MinGW — Minimalist GNU for Windows contains a version of the GNAT compiler.

The current version of MinGW (5.1.6) contains gcc-4.5.0. This includes a fully functional GNAT compiler. If the automatic downloader does not work correctly you can download the compiler directly: pick gcc-4.5.0-1 from MinGW/BaseSystem/GCC/Version4/

old instructions

The following list should help you with the installation. (I may have forgotten something — but this is wiki, just add to the list)

  1. Install MinGW-3.1.0-1.exe
    1. extract binutils-2.15.91-20040904-1.tar.gz
    2. extract mingw-runtime-3.5.tar.gz
    3. extract gcc-core-3.4.2-20040916-1.tar.gz
    4. extract gcc-ada-3.4.2-20040916-1.tar.gz
    5. extract gcc-g++-3.4.2-20040916-1.tar.gz (Optional)
    6. extract gcc-g77-3.4.2-20040916-1.tar.gz (Optional)
    7. extract gcc-java-3.4.2-20040916-1.tar.gz (Optional)
    8. extract gcc-objc-3.4.2-20040916-1.tar.gz (Optional)
    9. extract w32api-3.1.tar.gz
  2. Install mingw32-make-3.80.0-3.exe (Optional)
  3. Install gdb-5.2.1-1.exe (Optional)
  4. Install MSYS-1.0.10.exe (Optional)
  5. Install msysDTK-1.0.1.exe (Optional)
    1. extract msys-automake-1.8.2.tar.bz2 (Optional)
    2. extract msys-autoconf-2.59.tar.bz2 (Optional)
    3. extract msys-libtool-1.5.tar.bz2 (Optional)

I have made good experience in using D:\MinGW as target directory for all installations and extractions.

Also noteworthy is that the Windows version for GNAT from Libre is also based on MinGW.

In gcc-3.4.2-release_notes.txt from MinGW site reads: please check that the files in the /lib/gcc/mingw32/3.4.2/adainclude and adalib directories are flagged as read-only. This attribute is necessary to prevent them from being deleted when using gnatclean to clean a project.

So be sure to do this.

OpenCSW (for Solaris on SPARC and x86)

OpenCSW has binary packages of GCC 3.4.6 and 4.6.2 with Ada support. The package names are gcc3ada and gcc4ada respectively.

Languages supported Ada 83, Ada 95, parts of Ada 2005, C, C++, Fortran 95, Java, Objective-C, Objective-C++
License for the run-time library GNAT-modified GPL
Native platforms Oracle Solaris and OpenSolaris on SPARC and x86
Cross platforms none
Compiler back-end GCC 3.4.6 and 4.6.2 (both available)
Support ?
Available from http://www.opencsw.org/
Add-ons included none (?)

pkgsrc: NetBSD, DragonFly, FreeBSD and Solaris

The pkgsrc portable package file system has a small Ada framework. It is based on FSF GCC 5.4 currently and the FSF GCC 6.2 is available as well. The AdaCore GPL versions are not present, nor are non-GCC based compilers.

The pkgsrc system is released in quarterly branches, which are normally recommended. However, a user could also choose the "head" which would the very latest package versions. The pkgsrc system supports 21 platforms, but for Ada this is potentially limited to 5 due to the bootstrap compiler requirement: NetBSD, DragonFly, SunOS (Solaris/Illumos), OpenBSD/MirBSD, and FreeBSD.

Languages supported Ada 83, Ada 95, Ada 2005, Ada 2012, C, C++, ObjC, Fortran
License for the run-time library GPLv3 with Runtime Library Exception v3
Native platforms NetBSD i386 and amd64, DragonFly x86-64, FreeBSD i386 and amd64, Solaris i386 and x86_64
Cross platforms None
Compiler back-end GCC 5.4 (GCC 4.9 and 6 available)
Available from http://www.pkgsrc.org, status: http://www.pkgsrc.se
Support Volunteer; public bug database

There are two ways to install the software. The quickest and easiest way is to install prebuilt binaries using the command "pkg_add <pkg name>". For example, to install the GNAT Programming Studio and all of its dependencies including the GNAT compiler, all you need is one command:

pkg_add gps

If a specific package is not available, or the user just prefers to build from source (this can take a long time), then a typical command would be:

cd /usr/pkg/devel/gps && bmake install

As with the binary installation, if any dependencies are missing they will be built first, also from source.

Available software as of 14 December 2016

Directory Common Name version pkg name
cad/ghdl GNU VHDL simulator 0.32rc1 ghdl
devel/florist Florist (Posix Binding) 2012 florist-gpl
devel/gnatpython GNATPython (python-based test framework) 2011-09-12 gnatpython
devel/gprbuild-aux GPRbuild (Multi-language build tool) 2016-06-09 gprbuild-aux
lang/gcc-aux GNAT Ada compiler (FSF GCC) 4.9.2 (2014-10-23) gcc-aux
lang/gcc5-aux GNAT Ada compiler (FSF GCC) 5.4.0 (2016-06-03) gcc5-aux
lang/gcc6-aux GNAT Ada compiler (FSF GCC) 6.2.0 (2016-08-22) gcc6-aux
textproc/xmlada XML/Ada (Library) 4.4.0 xmlada
www/aws Ada Web Server 3.1.0.0 (w) aws
www/aws-demos Ada Web Server demos 3.1.0.0 (w) aws-demos
x11/gtkada GTK/Ada (bindings) 2.24.4 gtkada

SuSE Linux

All versions of SuSE Linux have a GNAT compiler included. SuSE versions 9.2 and higher also contains ASIS, Florist and GLADE libraries. The following two packages are needed:

gnat
gnat-runtime

For SuSE version 12.1, the compiler is in the package

 gcc46-ada
 libada46

For 64 bit system you will need the 32 bit compatibility packages as well:

gnat-32bit
gnat-runtime-32bit

Ubuntu

Ubuntu (and derivatives like Kubuntu, Xubuntu...) is a Debian-based Linux distribution, thus the installation process described above can be used. Graphical package managers like Synaptic or Adept can also be employed to select the Ada packages.

ICC from Irvine Compiler Corporation

Irvine Compiler Corporation provides native and cross compilers for various platforms.[5] The compiler and run-time system support development of certified, safety-critical software.

Commercial, proprietary. No-cost evaluation is possible on request. Royalty-free redistribution of the run-time system is allowed.

Janus/Ada 83 and 95 from RR Software

RR Software offers native compilers for MS-DOS, Microsoft Windows and various Unix and Unix-like systems, and a library for Windows GUI programming called CLAW. There are academic, personal and professional editions, as well as support options.

Janus/Ada 95 supports subset of Ada 2007 and Ada 2012 features.

Commercial but relatively cheap; proprietary.

MAXAda from Concurrent

Concurrent offers MAXAda, an Ada 95 compiler for Linux/Xeon and PowerPC platforms, and Ada bindings to POSIX and X/Motif.[6]

Commercial, proprietary.

ObjectAda from PTC (formerly Aonix/Atego)

PTC offers ObjectAda native (Windows, some flavors of Unix, and Linux) and cross (PPC, Intel, VxWorks, and ERC32) compilers.

Limited support of Ada 2012 is available.

Commercial, proprietary.

PowerAda from OC Systems

OC Systems offers Ada compilers and bindings to POSIX and X-11:

  • PowerAda, an Ada 95 compiler for Linux and AIX,
  • LegacyAda/390, an Ada 83 compiler for IBM System 370 and 390 mainframes

Commercial, proprietary.

ApexAda from PTC (formerly IBM Rational)

PTC ApexAda for native and embedded development.

Commercial, proprietary.

SCORE from DDC-I

DDC-I offers its SCORE cross-compilers for embedded development. SCORE stands for Safety-Critical, Object-oriented, Real-time Embedded.

Commercial, proprietary.

TADS from Tartan

Tartan offers the Tartan Ada Development System (TADS), with cross-compilers for some digital signal processors.

Commercial, proprietary.

XD Ada from DXC

XD Ada is an Ada 83 cross-compiler for embedded development. Hosts include VAX, Alpha and Integrity Servers running OpenVMS. Targets include Motorola 68000 and MIL-STD-1750A processors.

Commercial, proprietary.

XGC Ada from XGC Software

XGC compilers are GCC with custom run-time libraries suitable for avionics and space applications. The run-time kernels are very small and do not support exception propagation (i.e. you can handle an exception only in the subprogram that raised it).

Commercial but some versions are also offered as free downloads. Free Software.

Languages supported Ada 83, Ada 95, C
License for the run-time library GNAT-Modified GPL
Native platforms none
Cross platforms Hosts: sun-sparc-solaris, pc-linux2.*; targets are bare boards with ERC32, MIL-STD-1750A, Motorola 68000 family or Intel 32-bit processors. PowerPC and Intel 80186 targets on request.
Compiler back-end GCC 2.8.1
Available from http://www.xgc.com/
Support Commercial
Add-ons included Ravenscar-compliant run-time kernels, certified for avionics and space applications; gdb cross-debugger; target simulator.

References


Building

Ada programs are usually easier to build than programs written in other languages like C or C++, which frequently require a makefile. This is because an Ada source file already specifies the dependencies of its source unit. See the with keyword for further details.

Building an Ada program is not defined by the Reference Manual, so this process is absolutely dependent on the compiler. Usually the compiler kit includes a make tool which compiles a main program and all its dependencies, and links an executable file.

Building with various compilers

This list is incomplete. You can help Wikibooks by adding the build information for other compilers.

Alire

Alire is a build tool and package manager which automatically manages package dependencies including downloading the needed build tools. It is based on the GNAT compiler.

You can create a new project with alr init:

  Code:

alr init test

  Output:

Select the kind of crate you want to create:
  1. LIBRARY
  2. BINARY
Enter your choice index (first is default): 
> 1
Enter a short description of the crate: (default: '')
> Testing alr init
Select a software license for the crate?
  1. MIT OR Apache-2.0 WITH LLVM-exception
  2. MIT
  3. Apache-2.0 WITH LLVM-exception
  4. Apache-2.0
  5. BSD-3-Clause
  6. LGPL-3.0-or-later
  7. GPL-3.0-or-later WITH GCC-exception-3.1
  8. GPL-3.0-or-later
  9. Other...
Enter your choice index (first is default): 
> 8
Enter a comma (',') separated list of tags to help people find your crate: (default: '')
> test
Enter an optional Website URL for the crate: (default: '')
> https://wikibook-ada.sourceforge.net/
✓ test initialized successfully.

Alire supports most of the GNAT feature mentioned below as well as the same IDE and code editors.

GNAT

With GNAT, you can run this command:

gnat make <your_unit_file>

If the file contains a procedure, gnatmake will generate an executable file with the procedure as main program. Otherwise, e.g. a package, gnatmake will compile the unit and all its dependencies.

GNAT command line

gnatmake can be written as one word gnatmake or two words gnat make. For a full list of gnat commands just type gnat without any command options. The output will look something like this:

GNAT 3.4.3 Copyright 1996-2004 Free Software Foundation, Inc.

List of available commands

GNAT BIND               gnatbind
GNAT CHOP               gnatchop
GNAT CLEAN              gnatclean
GNAT COMPILE            gnatmake -c -f -u
GNAT ELIM               gnatelim
GNAT FIND               gnatfind
GNAT KRUNCH             gnatkr
GNAT LINK               gnatlink
GNAT LIST               gnatls
GNAT MAKE               gnatmake
GNAT NAME               gnatname
GNAT PREPROCESS         gnatprep
GNAT PRETTY             gnatpp
GNAT STUB               gnatstub
GNAT XREF               gnatxref

Commands FIND, LIST, PRETTY, STUB and XREF accept project file switches -vPx, -Pprj and -Xnam=val

For further help on the option just type the command (one word or two words — as you like) without any command options.

GNAT IDE

The GNAT toolchain comes with an IDE called GPS, included with recent releases of the GPL version of GNAT. GPS features a graphical user interface.

Emacs includes (Ada Mode), and GNAT plugins for KDevelop and Vim (Ada Mode) are available.

Vim Ada Mode is maintained by The GNU Ada project.

GNAT with Xcode

Apple's free (gratis) IDE, Xcode, uses the LLVM compiler with the Clang front-end, and does not support Ada: however, in Xcode 4.3 for OS X Lion and later versions, the command line tools (assembler, linker etc) which are required to use GNAT are an optional component of Xcode and must be specially installed.

Rational APEX

Rational APEX is a complete development environment comprising a language sensitive editor, compiler, debugger, coverage analyser, configuration management and much more. You normally work with APEX running a GUI.

APEX has been built for the development of big programs. Therefore the basic entity of APEX is a subsystem, a directory with certain traits recognized by APEX. All Ada compilation units have to reside in subsystems.

You can define an export set, i.e. the set of Ada units visible to other subsystems. However for a subsystem A to gain visibility to another subsystem B, A has to import B. After importing, A sees all units in B's export set. (This is much like the with-clauses, but here visibility means only potential visibility for Ada: units to be actually visible must be mentioned in a with-clause of course; units not in the export set cannot be used in with-clauses of Ada units in external subsystems.)

Normally subsystems should be hierarchically ordered, i.e. form a directed graph. But for special uses, subsystems can also mutually import one another.

For configuration management, a subsystem is decomposed in views, subdirectories of the subsystem. Views hold different development versions of the Ada units. So actually it's not subsystems which import other subsystems, rather subsystem views import views of other subsystems. (Of course, the closure of all imports must be consistent — it cannot be the case that e.g. subsystem (A, view A1) imports subsystems (B, B1) and (C, C1), whereas (B, B1) imports (C, C2)).

A view can be defined to be the development view. Other views then hold releases at different stages.

Each Ada compilation unit has to reside in a file of its own. When compiling an Ada unit, the compiler follows the with-clauses. If a unit is not found within the subsystem holding the compile, the compiler searches the import list (only the direct imports are considered, not the closure).

Units can be taken under version control. In each subsystem, a set of histories can be defined. An Ada unit can be taken under control in a history. If you want to edit it, you first have to check it out — it gets a new version number. After the changes, you can check it in again, i.e. make the changes permanent (or you abandon your changes, i.e. go back to the previous version). You normally check out units in the development view only; check-outs in release views can be forbidden.

You can select which version shall be the active one; normally it is the one latest checked in. You can even switch histories to get different development paths. e.g. different bodies of the same specification for different targets.

ObjectAda

ObjectAda is a set of tools for editing, compiling, navigating and debugging programs written in Ada. There are various editions of ObjectAda. With some editions you compile programs for the same platform and operating systems on which you run the tools. These are called native. With others, you can produce programs for different operating systems and platforms. One possible platform is the Java virtual machine.

These remarks apply to the native Microsoft Windows edition. You can run the translation tools either from the IDE or from the command line.

Whether you prefer to work from the IDE, or from the command line, a little bookkeeping is required. This is done by creating a project. Each project consists of a number of source files, and a number of settings like search paths for additional Ada libraries and other dependences. Each project also has at least one target. Typically, there is a debug target, and a release target. The names of the targets indicate their purpose. At one time you compile for debugging, typically during development, at other times you compile with different settings, for example when the program is ready for release. Some (all commercial?) editions of ObjectAda permit a Java (VM) target.

DEC Ada for VMS

DEC Ada is an Ada 83 compiler for VMS. While “DEC Ada” is probably the name most users know, the compiler is now called “HP Ada”. It had previously been known also by names of "VAX Ada" and "Compaq Ada".

DEC Ada uses a true library management system — so the first thing you need to do is create and activate a library:

ACS Library Create [MyLibrary]
ACS Set Library [MyLibrary]

When creating a library you already set some constraints like support for Long_Float or the available memory size. So carefully read

HELP ACS Library Create *

Then next step is to load your Ada sources into the library:

ACS Load [Source]*.ada

The sources don't need to be perfect at this stage but syntactically correct enough for the compiler to determine the packages declared and analyze the with statements. Dec Ada allows you to have more than one package in one source file and you have any filename convention you like. The purpose of ACS Load is the creation of the dependency tree between the source files.

Next you compile them:

ACS Compile *

Note that compile take the package name and not the filename. The wildcard * means all packages loaded. The compiler automatically determines the right order for the compilation so a make tool is not strictly needed.

Last but not least you link your file into an

ACS Link /Executable=[Executables]Main.exe Main

On large systems you might want to break sources down into several libraries — in which case you also need

ACS Merge /Keep *

to merge the content of the current library with the library higher up the hierarchy. The larger libraries should then be created with:

ACS Library Create /Large

This uses a different directory layout more suitable for large libraries.

DEC Ada IDE

Dec Ada comes without an IDE, however the DEC LSE as well as the Ada Mode of the Vim text editor support DEC Ada.

Compiling our Demo Source

Once you have downloaded our example programs, you might wonder how to compile them.

Unless you use Alire, you need to extract the sources. Use your favorite zip tool to achieve that. On extraction, a directory with the same name as the filename is created. Beware: WinZip might also create a directory equaling the filename, so Windows users need to be careful using the right option, otherwise they end up with wikibook-ada-1_2_0.src\wikibook-ada-1_2_0.

Once you extracted the files, you will find all sources in wikibook-ada-1_2_0/Source. You could compile them right there. For your convenience, we also provide ready-made project files for the following IDEs (If you find a directory for an IDE not named, it might be in the making and not actually work).

Alire

Alire is the simplest way to compile the sample code if you use Windows, macOS or Linux. With Alire, you don't even need to download and extract the source, as Alire will do everything for you. All you need to do after the installation of Alire itself is execute the following command in a terminal or CMD window:

Example for Unix like operating systems

  Code:

alr get "wikibook"

  Output:

ⓘ Deploying wikibook=1.0.1...                                            
############################################################################################################################################################################################# 100.0%############################################################################################################################################################################################# 100.0%

wikibook=1.0.1 successfully retrieved.
ⓘ Found 2 nested crates in /Work/wikibook_1.0.1_780ee70f:
   basic/basic=1.0.1: Samples for WikiBook Ada Programing: Basic Ada
   pragmas_restrictions/pragmas_restrictions=1.0.1: Samples for WikiBook Ada Programing: Pragmas Restrictions
Dependencies were solved as follows:

   +📦 gnat 13.2.1 (new,gnat_native,binary)

This will download all samples ordered by chapter. To then, for example, build and execute the first sample code from the Basic Ada chapter you type:

Example for Unix like operating systems

  Code:

 cd "wikibook_1.0.1_780ee70f/basic"
 alr build
 bin/hello_word_1

  Output:

ⓘ Synchronizing workspace...
Dependencies automatically updated as follows:                           

   +📦 gnat 13.2.1 (new,gnat_native,binary)

ⓘ Building basic=1.0.1/basic.gpr...                                        
Setup
   [mkdir]        object directory for project Basic
   [mkdir]        exec directory for project Basic
Compile
   [Ada]          hello_world_1.adb
   [Ada]          hello_world_2.adb
   [Ada]          hello_world_3.adb
Bind
   [gprbind]      hello_world_1.bexch
   [gprbind]      hello_world_2.bexch
   [gprbind]      hello_world_3.bexch
   [Ada]          hello_world_1.ali
   [Ada]          hello_world_2.ali
   [Ada]          hello_world_3.ali
Link
   [link]         hello_world_1.adb
   [link]         hello_world_2.adb
   [link]         hello_world_3.adb
✓ Build finished successfully in 0.90 seconds.

Hello World!

GNAT

You will find multi-target GNAT Project files and a multi-make Makefile file in wikibook-ada-2_0_0/GNAT. For i686 Linux and Windows, you can compile any demo using:

gnat make -P project_file

You can also open them inside the GPS with

gps -P project_file

For other target platform it is a bit more difficult since you need to tell the project files which target you want to create. The following options can be used:

style ("Debug", "Release")
you can define if you like a debug or release version so you can compare how the options affect size and speed.
os ("Linux", "OS2", "Windows_NT", "VMS")
choose your operating system. Since there is no Ada 2005 available for OS/2 don't expect all examples to compile.
target ("i686", "x86_64", "AXP")
choose your CPU — "i686" is any form of 32bit Intel or AMD CPU, "x86_64" is an 64 bit Intel or AMD CPU and if you have an "AXP" then you know it.

Remember to type all options as they are shown. To compile a debug version on x86-64 Linux you type:

gnat make -P project_file -Xstyle=Debug -Xos=Linux -Xtarget=x86_64

As said in the beginning there is also a makefile available that will automatically determine the target used. So if you have a GNU make you can save yourself a lot of typing by using:

make project

or even use

make all

to make all examples in debug and release in one go.

Each compile is stored inside its own directory which is created in the form of wikibook-ada-2_0_0/GNAT/OS-Target-Style. Empty directories are provided inside the archive.

Rational APEX

APEX uses the subsystem and view directory structure, so you will have to create those first and copy the source files into the view. After creating a view using the architecture model of your choice, use the menu option "Compile -> Maintenance -> Import Text Files". In the Import Text Files dialog, add "wikibook-ada-2_0_0/Source/*.ad?" to select the Ada source files from the directory you originally extracted to. Apex uses the file extensions .1.ada for specs and .2.ada for bodies — don't worry, the import text files command will change these automatically.

To link an example, select its main subprogram in the directory viewer and click the link button in the toolbar, or "Compile -> Link" from the menu. Double-click the executable to run it. You can use the shift-key modifier to bypass the link or run dialog.

ObjectAda

ObjectAda command-line

The following describes using the ObjectAda tools for Windows in a console window.

Before you can use the ObjectAda tools from the command line, make sure the PATH environment variable lists the directory containing the ObjectAda tools. Something like

set path=%path%;P:\Programs\Aonix\ObjectAda\bin

A minimal ObjectAda project can have just one source file. like the Hello World program provided in

File: hello_world_1.adb (view, plain text, download page, browse all)

To build an executable from this source file, follow these steps (assuming the current directory is a fresh one and contains the above mentioned source file):

  • Register your source files:
X:\some\directory> adareg hello_world_1.adb

This makes your sources known to the ObjectAda tools. Have a look at the file UNIT.MAP created by adareg in the current directory if you like seeing what is happening under the hood.

  • Compile the source file:
X:\some\directory> adacomp hello_world_1.adb
Front end of hello_world_1.adb succeeded with no errors.
  • Build the executable program:
X:\some\directory> adabuild hello_world_1
ObjectAda Professional Edition Version 7.2.2: adabuild
   Copyright (c) 1997-2002 Aonix.  All rights reserved.
Linking...
Link of hello completed successfully

Notice that you specify the name of the main unit as argument to adabuild, not the name of the source file. In this case, it is Hello_World_1 as in

procedure Hello_World_1 is

More information about the tools can be found in the user guide Using the command line interface, installed with the ObjectAda tools.



Control Statements

Conditionals

Conditional clauses are blocks of code that will only execute if a particular expression (the condition) is true.

if-else

The if-else statement is the simplest of the conditional statements. They are also called branches, as when the program arrives at an if statement during its execution, control will "branch" off into one of two or more "directions". An if-else statement is generally in the following form:


if condition then
    statement;
else
    other statement;
end if;

If the original condition is met, then all the code within the first statement is executed. The optional else section specifies an alternative statement that will be executed if the condition is false. Exact syntax will vary between programming languages, but the majority of programming languages (especially procedural and structured languages) will have some form of if-else conditional statement built-in. The if-else statement can usually be extended to the following form:


if condition then
    statement;
elsif condition then
    other statement;
elsif condition then
    other statement;
...
else
    another statement;
end if; 

Only one statement in the entire block will be executed. This statement will be the first one with a condition which evaluates to be true. The concept of an if-else-if structure is easier to understand with the aid of an example:


with Ada.Text_IO;
use  Ada.Text_IO;
...
type Degrees is new Float range -273.15 .. Float'Last;
...
Temperature : Degrees;
...
if Temperature >= 40.0 then
    Put_Line ("Wow!");
    Put_Line ("It's extremely hot");
elsif Temperature >= 30.0 then
    Put_Line ("It's hot");
elsif Temperature >= 20.0 then
    Put_Line ("It's warm");
elsif Temperature >= 10.0 then
    Put_Line ("It's cool");
elsif Temperature >= 0.0 then
    Put_Line ("It's cold");
else
    Put_Line ("It's freezing");
end if; 

Optimizing hints

When this program executes, the computer will check all conditions in order until one of them matches its concept of truth. As soon as this occurs, the program will execute the statement immediately following the condition and continue, without checking any other condition . For this reason, when you are trying to optimize a program, it is a good idea to sort your if-else conditions in descending probability. This will ensure that in the most common scenarios, the computer has to do less work, as it will most likely only have to check one or two "branches" before it finds the statement which it should execute. However, when writing programs for the first time, try not to think about this too much lest you find yourself undertaking premature optimization.

Having said all that, you should be aware that an optimizing compiler might rearrange your if statement at will when the statement in question is free from side effects. Among other techniques optimizing compilers might even apply jump tables and binary searches.


In Ada, conditional statements with more than one conditional do not use short-circuit evaluation by default. In order to mimic C/C++'s short-circuit evaluation, use and then or or else between the conditions.

case

Often it is necessary to compare one specific variable against several constant expressions. For this kind of conditional expression the case statement exists. For example:


case X is
   when 1 =>

      Walk_The_Dog;

   when 5 =>

      Launch_Nuke;

   when 8 | 10 =>

      Sell_All_Stock;

   when others =>

      Self_Destruct;

end case;

The subtype of X must be a discrete type, i.e. an enumeration or integer type.

In Ada, one advantage of the case statement is that the compiler will check the coverage of the choices, that is, all the values of the subtype of variable X must be present or a default branch when others must specify what to do in the remaining cases.

Unconditionals

Unconditionals let you change the flow of your program without a condition. You should be careful when using unconditionals. Often they make programs difficult to understand. Read Isn't goto evil? for more information.

return

End a function and return to the calling procedure or function.


For procedures:

return;

For functions:

return Value;

goto

Goto transfers control to the statement after the label.


   goto Label;

   Dont_Do_Something;

<<Label>>
   ...

Is goto evil?

Since Professor Dikstra wrote his article Go To Statement Considered Harmful, goto is considered bad practice in structured programming languages, and, in fact, many programming style guides forbid the use of the goto statement. But it is often overlooked that any return which is not the last statement inside a procedure or function is also an unconditional statement — a goto in disguise. There is an important difference though: a return is a forward only use of goto. Exceptions are also a type of goto statement; and note that they need not specify where they are going to.

Therefore, if you have functions and procedures with more than one return statement you are breaking the structure of the program similarly to a use of goto. In practice, nearly every programmer is familiar with a 'return' statement and its associated behavior; thus, when it comes down to readability the following two samples are almost the same:


Note also that the goto statement in Ada is safer than in other languages, since it doesn't allow you to transfer control:

  • outside a body;
  • between alternatives of a case statement, if statement, or select statement;
  • between different exception handlers; or from an exception handler of a handled_sequence_of_statements back to its sequence_of_statements.
procedure Use_Return is
begin
   Do_Something;

   if Test then
      return;
   end if;

   Do_Something_Else;

   return;
end Use_Return;
procedure Use_Goto is
begin
   Do_Something;
  
   if Test then
      goto Exit_Use_Goto;
   end if;

   Do_Something_Else;

<<Exit_Use_Goto>>
   return;
end Use_Goto;

Because the use of a goto needs the declaration of a label, the goto is in fact twice as readable than the use of return. So if readability is your concern and not a strict "don't use goto" programming rule then you should rather use goto than multiple returns. Best, of course, is the structured approach where neither goto nor multiple returns are needed:


procedure Use_If is
begin
   Do_Something;
  
   if not Test then

      Do_Something_Else;

   end if;

   return;
end Use_If;

Loops

Loops allow you to have a set of statements repeated over and over again.

Endless loop

The endless loop is a loop which never ends and the statements inside are repeated forever. The term, endless loop, is a relative term; if the running program is forcibly terminated by some means beyond the control of the program, then an endless loop will indeed end.


Endless_Loop :
   loop

      Do_Something;

   end loop Endless_Loop;

The loop name (in this case, "Endless_Loop") is an optional feature of Ada. Naming loops is nice for readability but not strictly needed. Loop names are useful though if the program should jump out of an inner loop, see below.

Loop with condition at the beginning

This loop has a condition at the beginning. The statements are repeated as long as the condition is met. If the condition is not met at the very beginning then the statements inside the loop are never executed.


While_Loop :
   while X <= 5 loop

      X := Calculate_Something;

   end loop While_Loop;

Loop with condition at the end

This loop has a condition at the end and the statements are repeated until the condition is met. Since the check is at the end the statements are at least executed once.


Until_Loop :
   loop

      X := Calculate_Something;

      exit Until_Loop when X > 5;
   end loop Until_Loop;

Loop with condition in the middle

Sometimes you need to first make a calculation and exit the loop when a certain criterion is met. However when the criterion is not met there is something else to be done. Hence you need a loop where the exit condition is in the middle.


Exit_Loop :
   loop

      X := Calculate_Something;

      exit Exit_Loop when X > 5;

      Do_Something (X);

   end loop  Exit_Loop;

In Ada the exit condition can be combined with any other loop statement as well. You can also have more than one exit statement. You can also exit a named outer loop if you have several loops inside each other.

for loop

Quite often one needs a loop where a specific variable is counted from a given start value up or down to a specific end value. You could use the while loop here — but since this is a very common loop there is an easier syntax available.


For_Loop :
   for I in Integer range 1 .. 10 loop

      Do_Something (I)

   end loop For_Loop;

You don't have to declare both subtype and range as seen in the example. If you leave out the subtype then the compiler will determine it by context; if you leave out the range then the loop will iterate over every value of the subtype given.

As always with Ada: when "determine by context" gives two or more possible options then an error will be displayed and then you have to name the type to be used. Ada will only do "guess-works" when it is safe to do so.

The loop counter I is a constant implicitly declared and ceases to exist after the body of the loop.

for loop on arrays

Another very common situation is the need for a loop which iterates over every element of an array. The following sample code shows you how to achieve this:


Array_Loop :
   for I in X'Range loop

      X (I) := Get_Next_Element;

   end loop Array_Loop;

With X being an array. Note: This syntax is mostly used on arrays — hence the name — but will also work with other types when a full iteration is needed.

Working example

The following example shows how to iterate over every element of an integer type.

File: range_1.adb (view, plain text, download page, browse all)
with Ada.Text_IO;

procedure Range_1 is
   type Range_Type is range -5 .. 10;

   package T_IO renames Ada.Text_IO;
   package I_IO is new  Ada.Text_IO.Integer_IO (Range_Type);

begin
   for A in Range_Type loop
      I_IO.Put (Item  => A,
                Width => 3,
                Base  => 10);

      if A < Range_Type'Last then
         T_IO.Put (",");
      else
         T_IO.New_Line;
      end if;
   end loop;
end Range_1;

See also

Wikibook

Ada Reference Manual


Type System

Ada's type system allows the programmer to construct powerful abstractions that represent the real world, and to provide valuable information to the compiler, so that the compiler can find many logic or design errors before they become bugs. It is at the heart of the language, and good Ada programmers learn to use it to great advantage. Four principles govern the type system:

  • Type: a way to categorize data. characters are types 'a' through 'z'. Integers are types that include 0,1,2....
  • Strong typing: types are incompatible with one another, so it is not possible to mix apples and oranges. The compiler will not guess that your apple is an orange. You must explicitly say my_fruit = fruit(my_apple). Strong typing reduces the amount of errors. This is because developers can really easily write a float into an integer variable without knowing. Now data you needed for your program to succeed has been lost in the conversion when the complier switched types. Ada gets mad and rejects the developer's dumb mistake by refusing to do the conversion unless explicitly told.
  • Static typing: type checked while compiling, this allows type errors to be found earlier.
  • Abstraction: types represent the real world or the problem at hand; not how the computer represents the data internally. There are ways to specify exactly how a type must be represented at the bit level, but we will defer that discussion to another chapter. An example of an abstraction is your car. You don't really know how it works you just know that bumbling hunk of metal moves. Nearly every technology you work with is abstracted layer to simplify the complex circuits that make it up - the same goes for software. You want abstraction because code in a class makes a lot more sense than a hundred if statements with no explanation when debugging
  • Name equivalence: as opposed to structural equivalence used in most other languages. Two types are compatible if and only if they have the same name; not if they just happen to have the same size or bit representation. You can thus declare two integer types with the same ranges that are totally incompatible, or two record types with exactly the same components, but which are incompatible.

Types are incompatible with one another. However, each type can have any number of subtypes, which are compatible with their base type and may be compatible with one another. See below for examples of subtypes which are incompatible with one another.

Predefined types

There are several predefined types, but most programmers prefer to define their own, application-specific types. Nevertheless, these predefined types are very useful as interfaces between libraries developed independently. The predefined library, obviously, uses these types too.

These types are predefined in the Standard package:

Integer
This type covers at least the range   ..   (RM 3.5.4: (21) [Annotated]). The Standard also defines Natural and Positive subtypes of this type.
Float
There is only a very weak implementation requirement on this type (RM 3.5.7: (14) [Annotated]); most of the time you would define your own floating-point types, and specify your precision and range requirements.
Duration
A fixed point type used for timing. It represents a period of time in seconds (RM A.1: (43) [Annotated]).
Character
A special form of Enumerations. There are three predefined kinds of character types: 8-bit characters (called Character), 16-bit characters (called Wide_Character), and 32-bit characters (Wide_Wide_Character). Character has been present since the first version of the language (Ada 83), Wide_Character was added in Ada 95, while the type Wide_Wide_Character is available with Ada 2005.
String
Three indefinite array types, of Character, Wide_Character, and Wide_Wide_Character respectively. The standard library contains packages for handling strings in three variants: fixed length (Ada.Strings.Fixed), with varying length below a certain upper bound (Ada.Strings.Bounded), and unbounded length (Ada.Strings.Unbounded). Each of these packages has a Wide_ and a Wide_Wide_ variant.
Boolean
A Boolean in Ada is an Enumeration of False and True with special semantics.

Packages System and System.Storage_Elements predefine some types which are primarily useful for low-level programming and interfacing to hardware.

System.Address
An address in memory.
System.Storage_Elements.Storage_Offset
An offset, which can be added to an address to obtain a new address. You can also subtract one address from another to get the offset between them. Together, Address, Storage_Offset and their associated subprograms provide for address arithmetic.
System.Storage_Elements.Storage_Count
A subtype of Storage_Offset which cannot be negative, and represents the memory size of a data structure (similar to C's size_t).
System.Storage_Elements.Storage_Element
In most computers, this is a byte. Formally, it is the smallest unit of memory that has an address.
System.Storage_Elements.Storage_Array
An array of Storage_Elements without any meaning, useful when doing raw memory access.

The Type Hierarchy

Types are organized hierarchically. A type inherits properties from types above it in the hierarchy. For example, all scalar types (integer, enumeration, modular, fixed-point and floating-point types) have operators "<", ">" and arithmetic operators defined for them, and all discrete types can serve as array indexes.

 
Ada type hierarchy

Here is a broad overview of each category of types; please follow the links for detailed explanations. Inside parenthesis there are equivalences in C and Pascal for readers familiar with those languages.

Signed Integers (int, INTEGER)
Signed Integers are defined via the range of values needed.
Unsigned Integers (unsigned, CARDINAL)
Unsigned Integers are called Modular Types. Apart from being unsigned they also have wrap-around functionality.
Enumerations (enum, char, bool, BOOLEAN)
Ada Enumeration types are a separate type family.
Floating point (float, double, REAL)
Floating point types are defined by the digits needed, the relative error bound.
Ordinary and Decimal Fixed Point (DECIMAL)
Fixed point types are defined by their delta, the absolute error bound.
Arrays ( [ ], ARRAY [ ] OF, STRING )
Arrays with both compile-time and run-time determined size are supported.
Record (struct, class, RECORD OF)
A record is a composite type that groups one or more fields.
Access (*, ^, POINTER TO)
Ada's Access types may be more than just a simple memory address.
Task & Protected (similar to multithreading in C++)
Task and Protected types allow the control of concurrency
Interfaces (similar to virtual methods in C++)
New in Ada 2005, these types are similar to the Java interfaces.

Classification of Types

Ada's types can be classified as follows.

Specific vs. Class-wide

type T is ...  --  a specific type
  T'Class      --  the corresponding class-wide type (exists only for tagged types)

T'Class and T'Class'Class are the same.

Primitive operations with parameters of specific types are non-dispatching, those with parameters of class-wide types are dispatching.

New types can be declared by deriving from specific types; primitive operations are inherited by derivation. You cannot derive from class-wide types.

Constrained vs. Unconstrained

type I is range 1 .. 10;           --  constrained
type AC is array (1 .. 10) of ...  --  constrained
type AU is array (I range <>) of ...          --  unconstrained
type R (X: Discriminant [:= Default]) is ...  --  unconstrained

By giving a constraint to an unconstrained subtype, a subtype or object becomes constrained:

subtype RC is R (Value);  --  constrained subtype of R
OC: R (Value);            --  constrained object of anonymous constrained subtype of R
OU: R;                    --  unconstrained object

Declaring an unconstrained object is only possible if a default value is given in the type declaration above. The language does not specify how such objects are allocated. GNAT allocates the maximum size, so that size changes that might occur with discriminant changes present no problem. Another possibility is implicit dynamic allocation on the heap and re-allocation followed by a deallocation when the size changes.

Definite vs. Indefinite

type I is range 1 .. 10;                     --  definite
type RD (X: Discriminant := Default) is ...  --  definite
type T (<>) is ...                    --  indefinite
type AU is array (I range <>) of ...  --  indefinite
type RI (X: Discriminant) is ...      --  indefinite

Definite subtypes allow the declaration of objects without initial value, since objects of definite subtypes have constraints that are known at creation-time. Object declarations of indefinite subtypes need an initial value to supply a constraint; they are then constrained by the constraint delivered by the initial value.

OT: T  := Expr;                       --  some initial expression (object, function call, etc.)
OA: AU := (3 => 10, 5 => 2, 4 => 4);  --  index range is now 3 .. 5
OR: RI := Expr;                       --  again some initial expression as above

Unconstrained vs. Indefinite

Note that unconstrained subtypes are not necessarily indefinite as can be seen above with RD: it is a definite unconstrained subtype.

Concurrency Types

The Ada language uses types for one more purpose in addition to classifying data + operations. The type system integrates concurrency (threading, parallelism). Programmers will use types for expressing the concurrent threads of control of their programs.

The core pieces of this part of the type system, the task types and the protected types are explained in greater depth in a section on tasking.

Limited Types

Limiting a type means disallowing assignment. The “concurrency types” described above are always limited. Programmers can define their own types to be limited, too, like this:

type T is limited …;

(The ellipsis stands for private, or for a record definition, see the corresponding subsection on this page.) A limited type also doesn't have an equality operator unless the programmer defines one.

You can learn more in the limited types chapter.

Defining new types and subtypes

You can define a new type with the following syntax:

type T is...

followed by the description of the type, as explained in detail in each category of type.

Formally, the above declaration creates a type and its first subtype named T. The type itself, correctly called the "type of T", is anonymous; the RM refers to it as T (in italics), but often speaks sloppily about the type T. But this is an academic consideration; for most purposes, it is sufficient to think of T as a type. For scalar types, there is also a base type called T'Base, which encompasses all values of T.

For signed integer types, the type of T comprises the (complete) set of mathematical integers. The base type is a certain hardware type, symmetric around zero (except for possibly one extra negative value), encompassing all values of T.

As explained above, all types are incompatible; thus:

type Integer_1 is range 1 .. 10;
type Integer_2 is range 1 .. 10;
A : Integer_1 := 8;
B : Integer_2 := A; -- illegal!

is illegal, because Integer_1 and Integer_2 are different and incompatible types. It is this feature which allows the compiler to detect logic errors at compile time, such as adding a file descriptor to a number of bytes, or a length to a weight. The fact that the two types have the same range does not make them compatible: this is name equivalence in action, as opposed to structural equivalence. (Below, we will see how you can convert between incompatible types; there are strict rules for this.)

Creating subtypes

You can also create new subtypes of a given type, which will be compatible with each other, like this:

type Integer_1 is range 1 .. 10;
subtype Integer_2 is Integer_1      range 7 .. 11;  -- bad
subtype Integer_3 is Integer_1'Base range 7 .. 11;  -- OK
A : Integer_1 := 8;
B : Integer_3 := A; -- OK

The declaration of Integer_2 is bad because the constraint 7 .. 11 is not compatible with Integer_1; it raises Constraint_Error at subtype elaboration time.

Integer_1 and Integer_3 are compatible because they are both subtypes of the same type, namely Integer_1'Base.

It is not necessary that the subtype ranges overlap, or be included in one another. The compiler inserts a run-time range check when you assign A to B; if the value of A, at that point, happens to be outside the range of Integer_3, the program raises Constraint_Error.

There are a few predefined subtypes which are very useful:

subtype Natural  is Integer range 0 .. Integer'Last;
subtype Positive is Integer range 1 .. Integer'Last;

Derived types

A derived type is a new, full-blown type created from an existing one. Like any other type, it is incompatible with its parent; however, it inherits the primitive operations defined for the parent type.

type Integer_1 is range 1 .. 10;
type Integer_2 is new Integer_1 range 2 .. 8;
A : Integer_1 := 8;
B : Integer_2 := A; -- illegal!

Here both types are discrete; it is mandatory that the range of the derived type be included in the range of its parent. Contrast this with subtypes. The reason is that the derived type inherits the primitive operations defined for its parent, and these operations assume the range of the parent type. Here is an illustration of this feature:

procedure Derived_Types is

   package Pak is
      type Integer_1 is range 1 .. 10;
      procedure P (I: in Integer_1); -- primitive operation, assumes 1 .. 10
      type Integer_2 is new Integer_1 range 8 .. 10; -- must not break P's assumption
      -- procedure P (I: in Integer_2);  inherited P implicitly defined here
   end Pak;

   package body Pak is
      -- omitted
   end Pak;

   use Pak;
   A: Integer_1 := 4;
   B: Integer_2 := 9;

begin

   P (B); -- OK, call the inherited operation

end Derived_Types;

When we call P (B), the parameter B is converted to Integer_1; this conversion of course passes since the set of acceptable values for the derived type (here, 8 .. 10) must be included in that of the parent type (1 .. 10). Then P is called with the converted parameter.

Consider however a variant of the example above:

procedure Derived_Types is

  package Pak is
    type Integer_1 is range 1 .. 10;
    procedure P (I: in Integer_1; J: out Integer_1);
    type Integer_2 is new Integer_1 range 8 .. 10;
  end Pak;

  package body Pak is
    procedure P (I: in Integer_1; J: out Integer_1) is
    begin
      J := I - 1;
    end P;
  end Pak;

  use Pak;

  A: Integer_1 := 4;  X: Integer_1;
  B: Integer_2 := 8;  Y: Integer_2;

begin

  P (A, X);
  P (B, Y);

end Derived_Types;

When P (B, Y) is called, both parameters are converted to Integer_1. Thus the range check on J (7) in the body of P will pass. However on return parameter Y is converted back to Integer_2 and the range check on Y will of course fail.

With the above in mind, you will see why in the following program Constraint_Error will be called at run time, before P is even called.

procedure Derived_Types is

  package Pak is
    type Integer_1 is range 1 .. 10;
    procedure P (I: in Integer_1; J: out Integer_1);
    type Integer_2 is new Integer_1'Base range 8 .. 12;
  end Pak;

  package body Pak is
    procedure P (I: in Integer_1; J: out Integer_1) is
    begin
      J := I - 1;
    end P;
  end Pak;

  use Pak;

  B: Integer_2 := 11;  Y: Integer_2;

begin

  P (B, Y);

end Derived_Types;

Subtype categories

Ada supports various categories of subtypes which have different abilities. Here is an overview in alphabetical order.

Anonymous subtype

A subtype which does not have a name assigned to it. Such a subtype is created with a variable declaration:

X : String (1 .. 10) := (others => ' ');

Here, (1 .. 10) is the constraint. This variable declaration is equivalent to:

subtype Anonymous_String_Type is String (1 .. 10);

X : Anonymous_String_Type := (others => ' ');

Base type

In Ada, all types are anonymous and only subtypes may be named. For scalar types, there is a special subtype of the anonymous type, called the base type, which is nameable with Subtype'Base notation. This Name'Attribute (read "name tick attribute") is the special notation used in Ada for what is called an attribute, i.e. a characteristic of a type, a variable, or some other program entity, that is defined by the compiler and can be queried. In this case, the base type (Subtype'Base) comprises all values of the first subtype. Some examples:

 type Int is range 0 .. 100;

The base type Int'Base is a hardware type selected by the compiler that comprises the values of Int. Thus, it may have the range -27 .. 27-1 or -215 .. 215-1 or any other such type.

 type Enum  is (A, B, C, D);
 type Short is new Enum range A .. C;

Enum'Base is the same as Enum, but Short'Base also holds the literal D.

Constrained subtype

A subtype of an indefinite subtype that adds constraints. The following example defines a 10 character string sub-type.

 subtype String_10 is String (1 .. 10);

You cannot partially constrain an unconstrained subtype:

 type My_Array is array (Integer range <>, Integer range <>) of Some_Type;

 --  subtype Constr is My_Array (1 .. 10, Integer range <>);  illegal

 subtype Constr is My_Array (1 .. 10, -100 .. 200);

Constraints for all indices must be given, the result is necessarily a definite subtype.

Definite subtype

A definite subtype is a subtype whose size is known at compile-time. All subtypes which are not indefinite subtypes are, by definition, definite subtypes.

Objects of definite subtypes may be declared without additional constraints.

Indefinite subtype

An indefinite subtype is a subtype whose size is not known at compile-time but is dynamically calculated at run-time. An indefinite subtype does not by itself provide enough information to create an object; an additional constraint or explicit initialization expression is necessary in order to calculate the actual size and therefore create the object.

X : String := "This is a string";

X is an object of the indefinite (sub)type String. Its constraint is derived implicitly from its initial value. X may change its value, but not its bounds.

It should be noted that it is not necessary to initialize the object from a literal. You can also use a function. For example:

X : String := Ada.Command_Line.Argument (1);

This statement reads the first command-line argument and assigns it to X.

A subtype of an indefinite subtype that does not add a constraint only introduces a new name for the original subtype (a kind of renaming under a different notion).

 subtype My_String is String;

My_String and String are interchangeable.

Named subtype

A subtype which has a name assigned to it. “First subtypes” are created with the keyword type (remember that types are always anonymous, the name in a type declaration is the name of the first subtype), others with the keyword subtype. For example:

type Count_To_Ten is range 1 .. 10;

Count_to_Ten is the first subtype of a suitable integer base type. However, if you would like to use this as an index constraint on String, the following declaration is illegal:

subtype Ten_Characters is String (Count_to_Ten);

This is because String has Positive as its index, which is a subtype of Integer (these declarations are taken from package Standard):

subtype Positive is Integer range 1 .. Integer'Last;

type String is (Positive range <>) of Character;

So you have to use the following declarations:

subtype Count_To_Ten is Integer range 1 .. 10;
subtype Ten_Characters is String (Count_to_Ten);

Now Ten_Characters is the name of that subtype of String which is constrained to Count_To_Ten. You see that posing constraints on types versus subtypes has very different effects.

Unconstrained subtype

Any indefinite type is also an unconstrained subtype. However, unconstrainedness and indefiniteness are not the same.

 type My_Enum is (A, B, C);
 type My_Record (Discriminant: My_Enum) is ...;

 My_Object_A: My_Record (A);

This type is unconstrained and indefinite because you need to give an actual discriminant for object declarations; the object is constrained to this discriminant which may not change.

When however a default is provided for the discriminant, the type is definite yet unconstrained; it allows to define both, constrained and unconstrained objects:

 type My_Enum is (A, B, C);
 type My_Record (Discriminant: My_Enum := A) is ...;

 My_Object_U: My_Record;      --  unconstrained object
 My_Object_B: My_Record (B);  --  constrained to discriminant B like above

Here, My_Object_U is unconstrained; upon declaration, it has the discriminant A (the default) which however may change.

Incompatible subtypes

 type My_Integer is range -10 .. + 10;
 subtype My_Positive is My_Integer range + 1 .. + 10;
 subtype My_Negative is My_Integer range -10 .. -  1;

These subtypes are of course incompatible.

Another example are subtypes of a discriminated record:

 type My_Enum is (A, B, C);
 type My_Record (Discriminant: My_Enum) is ...;
 subtype My_A_Record is My_Record (A);
 subtype My_C_Record is My_Record (C);

Also these subtypes are incompatible.

Qualified expressions

In most cases, the compiler is able to infer the type of an expression; for example:

type Enum is (A, B, C);
E : Enum := A;

Here the compiler knows that A is a value of the type Enum. But consider:

procedure Bad is
   type Enum_1 is (A, B, C);
   procedure P (E : in Enum_1) is... -- omitted
   type Enum_2 is (A, X, Y, Z);
   procedure P (E : in Enum_2) is... -- omitted
begin
   P (A); -- illegal: ambiguous
end Bad;

The compiler cannot choose between the two versions of P; both would be equally valid. To remove the ambiguity, you use a qualified expression:

   P (Enum_1'(A)); -- OK

As seen in the following example, this syntax is often used when creating new objects. If you try to compile the example, it will fail with a compilation error since the compiler will determine that 256 is not in range of Byte.

File: convert_evaluate_as.adb (view, plain text, download page, browse all)
with Ada.Text_IO;

procedure Convert_Evaluate_As is
   type Byte     is mod 2**8;
   type Byte_Ptr is access Byte;

   package T_IO renames Ada.Text_IO;
   package M_IO is new Ada.Text_IO.Modular_IO (Byte);

   A : constant Byte_Ptr := new Byte'(256);
begin
   T_IO.Put ("A = ");
   M_IO.Put (Item  => A.all,
             Width =>  5,
             Base  => 10);
end Convert_Evaluate_As;

You should use qualified expression when getting a string literal's length.

"foo"'Length                  {{Ada/--| compilation error: prefix of attribute must be a name}}
                              {{Ada/--|                    qualify expression to turn it into a name}}
String'("foo" & "bar")'Length {{Ada/--| 6}}

Type conversions

Data do not always come in the format you need them. You must, then, face the task of converting them. As a true multi-purpose language with a special emphasis on "mission critical", "system programming" and "safety", Ada has several conversion techniques. The most difficult part is choosing the right one, so the following list is sorted in order of utility. You should try the first one first; the last technique is a last resort, to be used if all others fail. There are also a few related techniques that you might choose instead of actually converting the data.

Since the most important aspect is not the result of a successful conversion, but how the system will react to an invalid conversion, all examples also demonstrate faulty conversions.

Explicit type conversion

An explicit type conversion looks much like a function call; it does not use the tick (apostrophe, ') like the qualified expression does.

Type_Name (Expression)

The compiler first checks that the conversion is legal, and if it is, it inserts a run-time check at the point of the conversion; hence the name checked conversion. If the conversion fails, the program raises Constraint_Error. Most compilers are very smart and optimise away the constraint checks; so, you need not worry about any performance penalty. Some compilers can also warn that a constraint check will always fail (and optimise the check with an unconditional raise).

Explicit type conversions are legal:

  • between any two numeric types
  • between any two subtypes of the same type
  • between any two types derived from the same type (note special rules for tagged types)
  • between array types under certain conditions (see RM 4.6(24.2/2..24.7/2))
  • and nowhere else

(The rules become more complex with class-wide and anonymous access types.)

I: Integer := Integer (10);  -- Unnecessary explicit type conversion
J: Integer := 10;            -- Implicit conversion from universal integer
K: Integer := Integer'(10);  -- Use the value 10 of type Integer: qualified expression
                             -- (qualification not necessary here).

This example illustrates explicit type conversions:

File: convert_checked.adb (view, plain text, download page, browse all)
with Ada.Text_IO;

procedure Convert_Checked is
   type Short is range -128 .. +127;
   type Byte  is mod 256;

   package T_IO renames Ada.Text_IO;
   package I_IO is new Ada.Text_IO.Integer_IO (Short);
   package M_IO is new Ada.Text_IO.Modular_IO (Byte);

   A : Short := -1;
   B : Byte;
begin
   B := Byte (A);  --  range check will lead to Constraint_Error
   T_IO.Put ("A = ");
   I_IO.Put (Item  =>  A,
             Width =>  5,
             Base  => 10);
   T_IO.Put (", B = ");
   M_IO.Put (Item  =>  B,
             Width =>  5,
             Base  => 10);
end Convert_Checked;

Explicit conversions are possible between any two numeric types: integers, fixed-point and floating-point types. If one of the types involved is a fixed-point or floating-point type, the compiler not only checks for the range constraints (thus the code above will raise Constraint_Error), but also performs any loss of precision necessary.

Example 1: the loss of precision causes the procedure to only ever print "0" or "1", since P / 100 is an integer and is always zero or one.

with Ada.Text_IO;
procedure Naive_Explicit_Conversion is
   type Proportion is digits 4 range 0.0 .. 1.0;
   type Percentage is range 0 .. 100;
   function To_Proportion (P : in Percentage) return Proportion is
   begin
      return Proportion (P / 100);
   end To_Proportion;
begin
   Ada.Text_IO.Put_Line (Proportion'Image (To_Proportion (27)));
end Naive_Explicit_Conversion;

Example 2: we use an intermediate floating-point type to guarantee the precision.

with Ada.Text_IO;
procedure Explicit_Conversion is
   type Proportion is digits 4 range 0.0 .. 1.0;
   type Percentage is range 0 .. 100;
   function To_Proportion (P : in Percentage) return Proportion is
      type Prop is digits 4 range 0.0 .. 100.0;
   begin
      return Proportion (Prop (P) / 100.0);
   end To_Proportion;
begin
   Ada.Text_IO.Put_Line (Proportion'Image (To_Proportion (27)));
end Explicit_Conversion;

You might ask why you should convert between two subtypes of the same type. An example will illustrate this.

subtype String_10 is String (1 .. 10);
X: String := "A line long enough to make the example valid";
Slice: constant String := String_10 (X (11 .. 20));

Here, Slice has bounds 1 and 10, whereas X (11 .. 20) has bounds 11 and 20.

Change of Representation

Type conversions can be used for packing and unpacking of records or arrays.

type Unpacked is record
  -- any components
end record;

type Packed is new Unpacked;
for  Packed use record
  -- component clauses for some or for all components
end record;
P: Packed;
U: Unpacked;

P := Packed (U);  -- packs U
U := Unpacked (P);  -- unpacks P

Checked conversion for non-numeric types

The examples above all revolved around conversions between numeric types; it is possible to convert between any two numeric types in this way. But what happens between non-numeric types, e.g. between array types or record types? The answer is two-fold:

  • you can convert explicitly between a type and types derived from it, or between types derived from the same type,
  • and that's all. No other conversions are possible.

Why would you want to derive a record type from another record type? Because of representation clauses. Here we enter the realm of low-level systems programming, which is not for the faint of heart, nor is it useful for desktop applications. So hold on tight, and let's dive in.

Suppose you have a record type which uses the default, efficient representation. Now you want to write this record to a device, which uses a special record format. This special representation is more compact (uses fewer bits), but is grossly inefficient. You want to have a layered programming interface: the upper layer, intended for applications, uses the efficient representation. The lower layer is a device driver that accesses the hardware directly and uses the inefficient representation.

package Device_Driver is
   type Size_Type is range 0 .. 64;
   type Register is record
      A, B : Boolean;
      Size : Size_Type;
   end record;

   procedure Read (R : out Register);
   procedure Write (R : in Register);
end Device_Driver;

The compiler chooses a default, efficient representation for Register. For example, on a 32-bit machine, it would probably use three 32-bit words, one for A, one for B and one for Size. This efficient representation is good for applications, but at one point we want to convert the entire record to just 8 bits, because that's what our hardware requires.

package body Device_Driver is
   type Hardware_Register is new Register; -- Derived type.
   for Hardware_Register use record
      A at 0 range 0 .. 0;
      B at 0 range 1 .. 1;
      Size at 0 range 2 .. 7;
   end record;

   function Get return Hardware_Register; -- Body omitted
   procedure Put (H : in Hardware_Register); -- Body omitted

   procedure Read (R : out Register) is
      H : Hardware_Register := Get;
   begin
      R := Register (H); -- Explicit conversion.
   end Read;

   procedure Write (R : in Register) is
   begin
      Put (Hardware_Register (R)); -- Explicit conversion.
   end Write;
end Device_Driver;

In the above example, the package body declares a derived type with the inefficient, but compact representation, and converts to and from it.

This illustrates that type conversions can result in a change of representation.

View conversion, in object-oriented programming

Within object-oriented programming you have to distinguish between specific types and class-wide types.

With specific types, only conversions in the direction to the root are possible, which of course cannot fail. There are no conversions in the opposite direction (where would you get the further components from?); extension aggregates have to be used instead.

With the conversion itself, no components of the source object that are not present in the target object are lost, they are just hidden from visibility. Therefore, this kind of conversion is called a view conversion since it provides a view of the source object as an object of the target type (especially it does not change the object's tag).

It is a common idiom in object oriented programming to rename the result of a view conversion. (A renaming declaration does not create a new object; it only gives a new name to something that already exists.)

type Parent_Type is tagged record
   <components>;
end record;
type Child_Type is new Parent_Type with record
   <further components>;
end record;

Child_Instance : Child_Type;
Parent_View    : Parent_Type renames Parent_Type (Child_Instance);
Parent_Part    : Parent_Type := Parent_Type (Child_Instance);

Parent_View is not a new object, but another name for Child_Instance viewed as the parent, i.e. only the parent components are visible, the child-specific components are hidden. Parent_Part, however, is an object of the parent type, which of course has no storage for the child-specific components, so they are lost with the assignment.

All types derived from a tagged type T form a tree rooted at T. The class-wide type T'Class can hold any object within this tree. With class-wide types, conversions in any direction are possible; there is a run-time tag check that raises Constraint_Error if the check fails. These conversions are also view conversions, no data is created or lost.

Object_1 : Parent_Type'Class := Parent_Type'Class (Child_Instance);
Object_2 : Parent_Type'Class renames Parent_Type'Class (Child_Instance);

Object_1 is a new object, a copy; Object_2 is just a new name. Both objects are of the class-wide type. Conversions to any type within the given class are legal, but are tag-checked.

Success : Child_Type := Child_Type (Parent_Type'Class (Parent_View));
Failure : Child_Type := Child_Type (Parent_Type'Class (Parent_Part));

The first conversion passes the tag check and both objects Child_Instance and Success are equal. The second conversion fails the tag check. (Conversion assignments of this kind will rarely be used; dispatching will do this automatically, see object oriented programming.)

You can perform these checks yourself with membership tests:

if Parent_View in Child_Type then ...
if Parent_View in Child_Type'Class then ...

There is also the package Ada.Tags.

Address conversion

Ada's access type is not just a memory location (a thin pointer). Depending on implementation and the access type used, the access might keep additional information (a fat pointer). For example GNAT keeps two memory addresses for each access to an indefinite object — one for the data and one for the constraint informations ('Size, 'First, 'Last).

If you want to convert an access to a simple memory location you can use the package System.Address_To_Access_Conversions. Note however that an address and a fat pointer cannot be converted reversibly into one another.

The address of an array object is the address of its first component. Thus, the bounds get lost in such a conversion.

type My_Array is array (Positive range <>) of Something;
A: My_Array (50 .. 100);

     A'Address = A(A'First)'Address

Unchecked conversion

One of the great criticisms of Pascal was "there is no escape". The reason was that sometimes you have to convert the incompatible. For this purpose, Ada has the generic function Unchecked_Conversion:

generic
   type Source (<>) is limited private;
   type Target (<>) is limited private;
function Ada.Unchecked_Conversion (S : Source) return Target;

Unchecked_Conversion will bit-copy the source data and reinterpret them under the target type without any checks. It is your chore to make sure that the requirements on unchecked conversion as stated in RM 13.9 (Annotated) are fulfilled; if not, the result is implementation dependent and may even lead to abnormal data. Use the 'Valid attribute after the conversion to check the validity of the data in problematic cases.

A function call to (an instance of) Unchecked_Conversion will copy the source to the destination. The compiler may also do a conversion in place (every instance has the convention Intrinsic).

To use Unchecked_Conversion you need to instantiate the generic.

In the example below, you can see how this is done. When run, the example will output A = -1, B = 255. No error will be reported, but is this the result you expect?

File: convert_unchecked.adb (view, plain text, download page, browse all)
with Ada.Text_IO;
with Ada.Unchecked_Conversion;

procedure Convert_Unchecked is

   type Short is range -128 .. +127;
   type Byte  is mod 256;

   package T_IO renames Ada.Text_IO;
   package I_IO is new Ada.Text_IO.Integer_IO (Short);
   package M_IO is new Ada.Text_IO.Modular_IO (Byte);

   function Convert is new Ada.Unchecked_Conversion (Source => Short,
                                                     Target => Byte);

   A : constant Short := -1;
   B : Byte;

begin

   B := Convert (A);
   T_IO.Put ("A = ");
   I_IO.Put (Item  =>  A,
             Width =>  5,
             Base  => 10);
   T_IO.Put (", B = ");
   M_IO.Put (Item  =>  B,
             Width =>  5,
             Base  => 10);

end Convert_Unchecked;

There is of course a range check in the assignment B := Convert (A);. Thus if B were defined as B: Byte range 0 .. 10;, Constraint_Error would be raised.

Overlays

If the copying of the result of Unchecked_Conversion is too much waste in terms of performance, then you can try overlays, i.e. address mappings. By using overlays, both objects share the same memory location. If you assign a value to one, the other changes as well. The syntax is:

for Target'Address use expression;
pragma Import (Ada, Target);

where expression defines the address of the source object.

While overlays might look more elegant than Unchecked_Conversion, you should be aware that they are even more dangerous and have even greater potential for doing something very wrong. For example if Source'Size < Target'Size and you assign a value to Target, you might inadvertently write into memory allocated to a different object.

You have to take care also of implicit initializations of objects of the target type, since they would overwrite the actual value of the source object. The Import pragma with convention Ada can be used to prevent this, since it avoids the implicit initialization, RM B.1 (Annotated).

The example below does the same as the example from "Unchecked Conversion".

File: convert_address_mapping.adb (view, plain text, download page, browse all)
with Ada.Text_IO;

procedure Convert_Address_Mapping is
   type Short is range -128 .. +127;
   type Byte  is mod 256;

   package T_IO renames Ada.Text_IO;
   package I_IO is new Ada.Text_IO.Integer_IO (Short);
   package M_IO is new Ada.Text_IO.Modular_IO (Byte);

   A : aliased Short;
   B : aliased Byte;
  
   for B'Address use A'Address;
  pragma Import (Ada, B);
  
begin
   A := -1;
   T_IO.Put ("A = ");
   I_IO.Put (Item  =>  A,
             Width =>  5,
             Base  => 10);
   T_IO.Put (", B = ");
   M_IO.Put (Item  =>  B,
             Width =>  5,
             Base  => 10);
end Convert_Address_Mapping;

Export / Import

Just for the record: There is still another method using the Export and Import pragmas. However, since this method completely undermines Ada's visibility and type concepts even more than overlays, it has no place here in this language introduction and is left to experts.

Elaborated Discussion of Types for Signed Integer Types

As explained before, a type declaration

type T is range 1 .. 10;

declares an anonymous type T and its first subtype T (please note the italicization). T encompasses the complete set of mathematical integers. Static expressions and named numbers make use of this fact.

All numeric integer literals are of type Universal_Integer. They are converted to the appropriate specific type where needed. Universal_Integer itself has no operators.

Some examples with static named numbers:

 S1: constant := Integer'Last + Integer'Last;       -- "+" of Integer
 S2: constant := Long_Integer'Last + 1;             -- "+" of Long_Integer
 S3: constant := S1 + S2;                           -- "+" of root_integer
 S4: constant := Integer'Last + Long_Integer'Last;  -- illegal

Static expressions are evaluated at compile-time on the appropriate types with no overflow checks, i.e. mathematically exact (only limited by computer store). The result is then implicitly converted to Universal_Integer.

The literal 1 in S2 is of type Universal_Integer and implicitly converted to Long_Integer.

S3 implicitly converts the summands to root_integer, performs the calculation and converts back to Universal_Integer.

S4 is illegal because it mixes two different types. You can however write this as

 S5: constant := Integer'Pos (Integer'Last) + Long_Integer'Pos (Long_Integer'Last);  -- "+" of root_integer

where the Pos attributes convert the values to Universal_Integer, which are then further implicitly converted to root_integer, added and the result converted back to Universal_Integer.

root_integer is the anonymous greatest integer type representable by the hardware. It has the range System.Min_Integer .. System.Max_Integer. All integer types are rooted at root_integer, i.e. derived from it. Universal_Integer can be viewed as root_integer'Class.

During run-time, computations of course are performed with range checks and overflow checks on the appropriate subtype. Intermediate results may however exceed the range limits. Thus with I, J, K of the subtype T above, the following code will return the correct result:

I := 10;
J :=  8;
K := (I + J) - 12;
-- I := I + J;  --  range check would fail, leading to Constraint_Error

Real literals are of type Universal_Real, and similar rules as the ones above apply accordingly.

Relations between types

Types can be made from other types. Array types, for example, are made from two types, one for the arrays' index and one for the arrays' components. An array, then, expresses an association, namely that between one value of the index type and a value of the component type.

 type Color is (Red, Green, Blue);
 type Intensity is range 0 .. 255;
 
 type Colored_Point is array (Color) of Intensity;

The type Color is the index type and the type Intensity is the component type of the array type Colored_Point. See array.

See also

Wikibook

Ada Reference Manual


Integer types

A range is a signed integer value which ranges from a First to a last Last. It is defined as

 range First .. Last

When a value is assigned to an object with such a range constraint, the value is checked for validity and Constraint_Error exception is raised when the value is not within First to Last.

When declaring a range type, the corresponding mathematical operators are implicitly declared by the language at the same place.

The compiler is free to choose a suitable underlaying hardware type for this user defined type.

Working example

The following example defines a new range from -5 to 10 and then prints the whole range out.

File: range_1.adb (view, plain text, download page, browse all)
with Ada.Text_IO;

procedure Range_1 is
   type Range_Type is range -5 .. 10;

   package T_IO renames Ada.Text_IO;
   package I_IO is new Ada.Text_IO.Integer_IO (Range_Type);

begin
   for A in Range_Type loop
      I_IO.Put (
         Item  => A,
         Width => 3,
         Base  => 10);

      if A < Range_Type'Last then
         T_IO.Put (",");
      else
         T_IO.New_Line;
      end if;
   end loop;
end Range_1;

See also

Wikibook

Ada Reference Manual

Unsigned integer types

Description

Unsigned integers in Ada have a value range from 0 to some positive number (not necessarily 1 subtracted from some power of 2). They are defined using the mod keyword because they implement a wrap-around arithmetic.

 mod Modulus

where 'First is 0 and 'Last is Modulus - 1.

Wrap-around arithmetic means that 'Last + 1 = 0 = 'First, and 'First - 1 = 'Last. Additionally to the normal arithmetic operators, bitwise and, or and xor are defined for the type (see below).

The predefined package Interfaces (RM B.2 [Annotated]) presents unsigned integers based on powers of 2

type Unsigned_n is mod 2**n;

for which also shift and rotate operations are defined. The values of n depend on compiler and target architecture.

You can use range to sub-range a modular type:

type Byte is mod 256;
subtype Half_Byte is Byte range 0 .. 127;

But beware: the Modulus of Half_Byte is still 256! Arithmetic with such a type is interesting to say the least.

Bitwise Operations

Be very careful with bitwise operators and, or, xor, not, when the modulus is not a power of two. An example might exemplify the problem.

type Unsigned is mod 2**5;   -- modulus 32
X: Unsigned := 2#10110#;     -- 22
not X        = 2#01001#      -- bit reversal: 9 ( = 31 - 22 ) as expected

The other operators work similarly.

Now take a modulus that is not a power of two. Naive expectations about the results may lead out of the value range. As an example take again the not operator (see the RM for the others):

type Unsigned is mod 5;
X: Unsigned := 2#001#;  -- 1, bit reversal: 2#110# = 6 leads out of range

The definition of not is therefore:

 not X = Unsigned'Last – X  -- here: 4 – 1 = 2#011#

See also

Wikibook

Ada Reference Manual


Enumerations

An enumeration type is defined as a list of possible values:

 type Primary_Color is (Red, Green, Blue);

Like for numeric types, where e.g. 1 is an integer literal, Red, Green and Blue are called the literals of this type. There are no other values assignable to objects of this type.

Operators and attributes

Apart from equality ("="), the only operators on enumeration types are the ordering operators: "<", "<=", "=", "/=", ">=", ">", where the order relation is given implicitly by the sequence of literals: Each literal has a position, starting with 0 for the first, incremented by one for each successor. This position can be queried via the 'Pos attribute; the inverse is 'Val, which returns the corresponding literal. In our example:

Primary_Color'Pos (Red) = 0
Primary_Color'Val (0)   = Red

There are two other important attributes: Image and Value (don't confuse Val with Value). Image returns the string representation of the value (in capital letters), Value is the inverse:

Primary_Color'Image ( Red ) = "RED"
Primary_Color'Value ("Red") =  Red

These attributes are important for simple IO (there are more elaborate IO facilities in Ada.Text_IO for enumeration types). Note that, since Ada is case-insensitive, the string given to 'Value can be in any case.

Enumeration literals

Literals are overloadable, i.e. you can have another type with the same literals.

type Traffic_Light is (Red, Yellow, Green);

Overload resolution within the context of use of a literal normally resolves which Red is meant. Only if you have an unresolvable overloading conflict, you can qualify with special syntax which Red is meant:

Primary_Color'(Red)

Like many other declarative items, enumeration literals can be renamed. In fact, such a literal is actually a function, so it has to be renamed as such:

function Red return P.Primary_Color renames P.Red;

Here, Primary_Color is assumed to be defined in package P, which is visible at the place of the renaming declaration. Renaming makes Red directly visible without necessity to resort the use-clause.

Note that redeclaration as a function does not affect the staticness of the literal.

Characters as enumeration literals

Rather unique to Ada is the use of character literals as enumeration literals:

 type ABC is ('A', 'B', 'C');

This literal 'A' has nothing in common with the literal 'A' of the predefined type Character (or Wide_Character).

Every type that has at least one character literal is a character type. For every character type, string literals and the concatenation operator "&" are also implicitly defined.

type My_Character is (No_Character, 'a', Literal, 'z');
type My_String is array (Positive range <>) of My_Character;

S: My_String := "aa" & Literal & "za" & 'z';
T: My_String := ('a', 'a', Literal, 'z', 'a', 'z');

In this example, S and T have the same value.

Ada's Character type is defined that way. See Ada Programming/Libraries/Standard.

Booleans as enumeration literals

Also Booleans are defined as enumeration types:

 type Boolean is (False, True);

There is special semantics implied with this declaration in that objects and expressions of this type can be used as conditions. Note that the literals False and True are not Ada keywords.

Thus it is not sufficient to declare a type with these literals and then hope objects of this type can be used like so:

 type My_Boolean is (False, True);
 Condition: My_Boolean;

 if Condition then -- wrong, won't compile

If you need your own Booleans (perhaps with special size requirements), you have to derive from the predefined Boolean:

 type My_Boolean is new Boolean;
 Condition: My_Boolean;

 if Condition then -- OK

Enumeration subtypes

You can use range to subtype an enumeration type:

 subtype Capital_Letter is Character range 'A' .. 'Z';
 type Day_Of_Week is (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday);
 
 subtype Working_Day is Day_Of_Week range Monday .. Friday;

Using enumerations

Enumeration types being scalar subtypes, type attributes such as First and Succ will allow stepping through a subsequence of the values.

     case Day_Of_Week'First is
        when Sunday =>
           ISO (False);
        when Day_Of_Week'Succ (Sunday) =>
           ISO (True);
        when Tuesday .. Saturday =>
           raise Program_Error;
     end case;

A loop will automatically step through the values of the subtype's range. Filtering week days to include only working days with an even position number:

     for Day in Working_Day loop
        if Day_Of_Week'Pos (Day) mod 2 = 0 then
           Work_In_Backyard;
        end if;
     end loop;

Enumeration types can be used as array index subtypes, yielding a table feature:

  type Officer_ID is range 0 .. 50;
  type Schedule is array (Working_Day) of Officer_ID;

See also

Wikibook

Ada Reference Manual

Floating point types

Description

To define a floating point type, you only have to say how many digits are needed, i.e. you define the relative precision:

digits Num_Digits

If you like, you can declare the minimum range needed as well:

digits Num_Digits range Low .. High

This facility is a great benefit of Ada over (most) other programming languages. In other languages, you just choose between "float" and "long float", and what most people do is:

  • choose float if they don't care about accuracy
  • otherwise, choose long float, because it is the best you can get

In either case, you don't know what accuracy you get.

In Ada, you specify the accuracy you need, and the compiler will choose an appropriate floating point type with at least the accuracy you asked for. This way, your requirement is guaranteed. Moreover, if the computer has more than two floating point types available, the compiler can make use of all of them.

See also

Wikibook

Ada Reference Manual

Fixed point types

Description

A fixed point type defines a set of values that are evenly spaced with a given absolute precision. In contrast, floating point values are all spaced according to a relative precision.

The absolute precision is given as the delta of the type. There are two kinds of fixed point types, ordinary and decimal.

For Ordinary Fixed Point types, the delta gives a hint to the compiler how to choose the small value if it is not specified: It can be any integer power of two not greater than delta. You may specify the small via an attribute clause to be any value not greater than delta. (If the compiler cannot conform to this small value, it has to reject the declaration.)

For Decimal Fixed Point types, the small is defined to be the delta, which in turn must be an integer power of ten. (Thus you cannot specify the small by an attribute clause.)

For example, if you define a decimal fixed point type with a delta of 0.1, you will be able to accurately store the values 0.1, 1.0, 2.2, 5.7, etc. You will not be able to accurately store the value 0.01. Instead, the value will be rounded down to 0.0.

If the compiler accepts your fixed point type definition, it guarantees that values represented by that type will have at least the degree of accuracy specified (or better). If the compiler cannot support the type definition (e.g. due to limited hardware) then a compile-time error will result.

Ordinary Fixed Point

For an ordinary fixed point, you just define the delta and a range:

delta Delta range Low .. High

The delta can be any real value — for example you may define a circle with one arcsecond resolution with:

delta 1.0 / (60 * 60) range 0.0 .. 360.0

[There is one rather strange rule about fixed point types: Because of the way they are internally represented, the range might only go up to 'Last - Delta. This is a bit like a circle — the 0° and 360° mark is also the same.]

It should be noted that in the example above the smallest possible value used is not  . The compiler will choose a smaller value which, by default, is an integer power of 2 not greater than the delta. In our example this could be  . In most cases this should render better performance but sacrifices precision for it.

If this is not what you wish and precision is indeed more important, you can choose your own small value via the attribute clause 'Small.

type Angle is delta Pi/2.0**31 range -Pi .. Pi;
for Angle'Small use Pi/2.0**31;

As internal representation, you will get a 32 bit signed integer type.

Decimal Fixed Point

You define a decimal fixed point by defining the delta and the number of digits needed:

 delta Delta digits Num_Digits

Delta must be a positive or negative integer power of 10 — otherwise the declaration is illegal.

delta 10.0**(+2) digits 12
delta 10.0**(-2) digits 12

If you like, you can also define the range needed:

delta Delta_Value digits Num_Digits range Low .. High

Differences between Ordinary and Decimal Fixed Point Types

There is an alternative way of declaring a "decimal" fixed point: You declare an ordinary fixed point and use an integer power of 10 as 'Small. The following two declarations are equivalent with respect to the internal representation:

-- decimal fixed point

type Duration is delta 10.0**(-9) digits 9;
-- ordinary fixed point

type Duration is delta 10.0**(-9) range -1.0 .. 1.0;
for Duration'Small use 10.0**(-9);

You might wonder what the difference then is between these two declarations. The answer is:

None with respect to precision, addition, subtraction, multiplication with integer values.

The following is an incomplete list of differences between ordinary and decimal fixed point types.

  • Decimal fixed point types are intended to reflect typical COBOL declarations with a given number of digits.
  • Truncation is required for decimal, not for ordinary, fixed point in multiplication and division (RM 4.5.5: (21) [Annotated]) and type conversions. Operations on decimal fixed point are fully specified, which is not true for ordinary fixed point.
  • The following attributes are only defined for decimal fixed point: T'Digits (RM 3.5.10: (10) [Annotated]) corresponds to the number of decimal digits that are representable; T'Scale (RM 3.5.10: (11) [Annotated], taken from COBOL) indicates the position of the point relative to the rightmost significant digits; T'Round (RM 3.5.10: (12) [Annotated]) can be used to specify rounding on conversion.
  • Package Decimal (RM F.2 [Annotated]), which of course applies only to decimal fixed point, defines the decimal Divide generic procedure. If annex F is supported (GNAT does), at least 18 digits must be supported (there is no such rule for fixed point).
  • Static expressions must be a multiple of the Small for decimal fixed point.

Conclusion: For normal numeric use, an ordinary fixed point (probably with 'Small defined) should be defined. Only if you are interested in COBOL like use, i.e. well defined deterministic decimal semantics (especially for financial computations, but that might apply to cases other than money) should you take decimal fixed point.

See also

Wikibook

Ada 95 Reference Manual

Ada 2005 Reference Manual

Arrays

An array is a collection of elements which can be accessed by one or more index values. In Ada any definite type is allowed as element and any discrete type, i.e. Range, Modular or Enumeration, can be used as an index.

Declaring arrays

Ada's arrays are quite powerful and so there are quite a few syntax variations, which are presented below.

Basic syntax

The basic form of an Ada array is:

array (Index_Range) of Element_Type

where Index_Range is a range of values within a discrete index type, and Element_Type is a definite subtype. The array consists of one element of "Element_Type" for each possible value in the given range. If you for example want to count how often a specific letter appears inside a text, you could use:

type Character_Counter is array (Character) of Natural;

Very often, the index does not have semantic contents by itself, it is just used a means to identify elements for instance in a list. Thus, as a general advice, do not use negative indices in these cases. It is also a good style when using numeric indices, to define them starting in 1 instead of 0, since it is more intuitive for humans and avoids off-by-one errors.

There are, however, cases, where negative indices make sense. So use indices adapted to the problem at hand. Imagine you are a chemist doing some experiments depending on the temperature:

type Temperature is range -10 .. +40;  -- Celsius

type Experiment is array (Temperature ) of Something;

With known subrange

Often you don't need an array of all possible values of the index type. In this case you can subtype your index type to the actually needed range.

subtype Index_Sub_Type is Index_Type range First .. Last

array (Index_Sub_Type) of Element_Type

Since this may involve a lot of typing and you may also run out of useful names for new subtypes, the array declaration allows for a shortcut:

array (Index_Type range First .. Last) of Element_Type

Since First and Last are expressions of Index_Type, a simpler form of the above is:

array (First .. Last) of Element_Type

Note that if First and Last are numeric literals, this implies the index type Integer.

If in the example above the character counter should only count upper case characters and discard all other characters, you can use the following array type:

type Character_Counter is array (Character range 'A' .. 'Z') of Natural;

With unknown subrange

Sometimes the range actually needed is not known until runtime or you need objects of different lengths. In some languages you would resort to pointers to element types. Not with Ada. Here we have the box '<>', which allows us to declare indefinite arrays:

array (Index_Type range <>) of Element_Type;

When you declare objects of such a type, the bounds must of course be given and the object is constrained to them.

The predefined type String is such a type. It is defined as

 type String is array (Positive range <>) of Character;

You define objects of such an unconstrained type in several ways (the extrapolation to other arrays than String should be obvious):

 Text : String (10 .. 20);
 Input: String := Read_from_some_file;

(These declarations additionally define anonymous subtypes of String.) In the first example, the range of indices is explicitly given. In the second example, the range is implicitly defined from the initial expression, which here could be via a function reading data from some file. Both objects are constrained to their ranges, i.e. they cannot grow nor shrink.

With aliased elements

If you come from C/C++, you are probably used to the fact that every element of an array has an address. The C/C++ standards actually demand that.

In Ada, this is not true. Consider the following array:

 type Day_Of_Month is range 1 .. 31;
 type Day_Has_Appointment is array (Day_Of_Month) of Boolean;
 pragma Pack (Day_Has_Appointment); 

Since we have packed the array, the compiler will use as little storage as possible. And in most cases this will mean that 8 boolean values will fit into one byte.

So Ada knows about arrays where more than one element shares one address. So what if you need to address each single element. Just not using pragma Pack is not enough. If the CPU has very fast bit access, the compiler might pack the array without being told. You need to tell the compiler that you need to address each element via an access.

 type Day_Of_Month is range 1 .. 31;
 type Day_Has_Appointment is array (Day_Of_Month) of aliased Boolean;

Arrays with more than one dimension

Arrays can have more than one index. Consider the following 2-dimensional array:

  type Character_Display is
     array (Positive range <>, Positive range <>) of Character;

This type permits declaring rectangular arrays of characters. Example:

  Magic_Square: constant Character_Display :=
     (('S', 'A', 'T', 'O', 'R'),
      ('A', 'R', 'E', 'P', 'O'),
      ('T', 'E', 'N', 'E', 'T'),
      ('O', 'P', 'E', 'R', 'A'),
      ('R', 'O', 'T', 'A', 'S'));

Or, stating some index values explicitly,

  Magic_Square: constant Character_Display(1 .. 5, 1 .. 5) :=
     (1 => ('S', 'A', 'T', 'O', 'R'),
      2 => ('A', 'R', 'E', 'P', 'O'),
      3 => ('T', 'E', 'N', 'E', 'T'),
      4 => ('O', 'P', 'E', 'R', 'A'),
      5 => ('R', 'O', 'T', 'A', 'S'));

The index values of the second dimension, those indexing the characters in each row, are in 1 .. 5 here. By choosing a different second range, we could change these to be in 11 .. 15:

  Magic_Square: constant Character_Display(1 .. 5, 11 .. 15) :=
     (1 => ('S', 'A', 'T', 'O', 'R'),
       ...

By adding more dimensions to an array type, we could have squares, cubes (or « bricks »), etc., of homogenous data items.

Finally, an array of characters is a string (see Ada Programming/Strings). Therefore, Magic_Square may simply be declared like this:

  Magic_Square: constant Character_Display :=
     ("SATOR",
      "AREPO",
      "TENET",
      "OPERA",
      "ROTAS");

Using arrays

Assignment

When accessing elements, the index is specified in parentheses. It is also possible to access slices in this way:

Vector_A (1 .. 3) := Vector_B (3 .. 5);

Note that the index range slides in this example: After the assignment, Vector_A (1) = Vector_B (3) and similarly for the other indices.

Also note that slice assignments are done in one go, not in a loop character by character, so that assignments with overlapping ranges work as expected:

Name: String (1 .. 13) := "Lady Ada     ";

Name (6 .. 13) := Name (1 .. 8);

The result is "Lady Lady Ada" (and not "Lady Lady Lad").

Slicing

As has been shown above, in slice assignments index ranges slide. Also subtype conversions make index ranges slide:

subtype Str_1_8 is String (1 .. 8);

The result of Str_1_8 (Name (6 .. 13)) has the new bounds 1 and 8 and contents "Lady Ada" and is not a copy. This is the best way to change the bounds of an array or of parts thereof.

Concatenate

The operator "&" can be used to concatenate arrays:

Name := First_Name & ' ' & Last_Name;

In both cases, if the resulting array does not fit in the destination array, Constraint_Error is raised.

If you try to access an existing element by indexing outside the array bounds, Constraint_Error is raised (unless checks are suppressed).

Array Attributes

There are four Attributes which are important for arrays: 'First, 'Last, 'Length and 'Range. Lets look at them with an example. Say we have the following three strings:

Hello_World  : constant String := "Hello World!";
World        : constant String := Hello_World (7 .. 11);
Empty_String : constant String := "";

Then the four attributes will have the following values:

Array 'First 'Last 'Length 'Range
Hello_World 1 12 12 1 .. 12
World 7 11 5 7 .. 11
Empty_String 1 0 0 1 .. 0

The example was chosen to show a few common beginner's mistakes:

  1. The assumption that strings begin with the index value 1 is wrong (cf. World'First = 7 on the second line).
  2. The assumption (which follows from the first one) that X'Length = X'Last is wrong.
  3. The assumption that X'Last >= X'First; this is not true for empty strings.

The index subtype of predefined type String is Positive, therefore excluding 0 or -17 etc. from the set of possible index values, by subtype constraint (of Positive). Also, 'A' or 2.17e+4 are excluded, since they are not of type Positive.

The attribute 'Range is a little special as it does not return a discrete value but an abstract description of the array. One might wonder what it is good for. The most common use is in the for loop on arrays. When looping over all elements of an array, you need not know the actual index range; by using the attribute, one of the most frequent errors, accessing elements out of the index range, can never occur:

 for I in World'Range loop
   ... World (I)...
 end loop;

'Range can also be used in declaring a name for the index subtype:

subtype Hello_World_Index is Integer range Hello_World'Range;

The Range attribute can be convenient when programming index checks:

if K in World'Range then
   return World(K);
else
   return Substitute;
end if;

Empty or Null Arrays

As you have seen in the section above, Ada allows for empty arrays. Any array whose last index value is lower than the first index value is empty. And — of course — you can have empty arrays of all sorts, not just String:

type Some_Array is array (Positive range <>) of Boolean;

Empty_Some_Array : constant Some_Array (1 .. 0) := (others => False);

Also_Empty: Some_Array (42 .. 10);

Note: If you give an initial expression to an empty array (which is a must for a constant), the expression in the aggregate will of course not be evaluated since there are no elements actually stored.

See also

Wikibook

Ada 95 Reference Manual

Ada 2005 Reference Manual

Ada Quality and Style Guide

Records

A record is a composite type that groups one or more fields. A field can be of any type, even a record.

Basic record

 type Basic_Record is
    record
       A : Integer;
    end record;

Null record

The null record is when a type without data is needed. There are two ways to declare a null record:

type Null_Record is
   record
      null;
   end record; 
type Null_Record is null record;

For the compiler they are the same. However, programmers often use the first variant if the type is not finished yet to show that they are planning to expand the type later, or they usually use the second if the (tagged) record is a base class in object oriented programming.

Record Values

Values of a record type can be specified using a record aggregate, giving a list of named components thus

  A_Basic_Record       : Basic_Record         := Basic_Record'(A => 42);
  Another_Basic_Record : Basic_Record         := (A => 42);
  Nix                  : constant Null_Record := (null record);

Given a somewhat larger record type,

  type Car is record
     Identity       : Long_Long_Integer;
     Number_Wheels  : Positive range 1 .. 10;
     Paint          : Color;
     Horse_Power_kW : Float range 0.0 .. 2_000.0;
     Consumption    : Float range 0.0 .. 100.0;
  end record;

a value may be specified using positional notation, that is, specifying a value for each record component in declaration order

  BMW : Car := (2007_752_83992434, 5, Blue, 190.0, 10.1);

However, naming the components of a Car aggregate offers a number of advantages.

  1. Easy identification of which value is used for which component. (After all, named components are the raison d'être of records.)
  2. Reordering the components is allowed—you only have to remember the component names, not their position.
  3. Improved compiler diagnostic messages.

Reordering components is possible because component names will inform the compiler (and the human reader!) of the intended value associations. Improved compiler messages are also in consequence of this additional information passed to the compiler. While an omitted component will always be reported due to Ada's coverage rules, messages can be much more specific when there are named associations. Considering the Car type from above, suppose a programmer by mistake specifies only one of the two floating point values for BMW in positional notation. The compiler, in search of another component value, will then not be able to decide whether the specified value is intended for Horse_Power_kW or for Consumption. If the programmer instead uses named association, say Horse_Power_kW => 190.0, it will be clear which other component is missing.

  BMW : Car :=
    (Identity       => 2007_752_83992434,
     Number_Wheels  => 5,
     Horse_Power_kW => 190.0,
     Consumption    => 10.1,
     Paint          => Blue);

In order to access a component of a record instance, use the dot delimiter (.), as in BMW.Number_Wheels.

Discriminated record

 type Discriminated_Record (Size : Natural) is 
    record
       A : String (1 .. Size);
    end record;
 
 ...
 
 Item : Discriminated_Record := (Size => Value'Length, A => Value);

Variant record

The variant record is a special type of discriminated record where the presence of some components depend on the value of the discriminant.

 type Traffic_Light is (Red, Yellow, Green);

 type Variant_Record (Option : Traffic_Light) is 
    record
       -- common components
       
       case Option is
          when Red =>
             -- components for red
          when Yellow =>
             -- components for yellow
          when Green =>
             -- components for green
       end case;
    end record;

Mutable and immutable variant records

You can declare variant record types such that its discriminant, and thus its variant structure, can be changed during the lifetime of the variable. Such a record is said to be mutable. When "mutating" a record, you must assign all components of the variant structure which you are mutating at once, replacing the record with a complete variant structure. Although a variant record declaration may allow objects of its type to be mutable, there are certain restrictions on whether the objects will be mutable. Reasons restricting an object from being mutable include:

  • the object is declared with a discriminant (see Immutable_Traffic_Light below)
  • the object is aliased (either by use of aliased in the object declaration, or by allocation on the heap using new)


 type Traffic_Light is (Red, Yellow, Green);

 type Mutable_Variant_Record (Option : Traffic_Light := Red) is      -- the discriminant must have a default value
    record
       -- common components
       Location : Natural;
       case Option is
          when Red =>
             -- components for red
             Flashing : Boolean := True;
          when Yellow =>
             -- components for yellow
             Timeout    : Duration := 0.0;
          when Green =>
             -- components for green
             Whatever : Positive := 1;
       end case;
    end record;
...
Mutable_Traffic_Light   : Mutable_Variant_Record;                    -- not declaring a discriminant makes this record mutable
                                                                     -- it has the default discriminant/variant
                                                                     -- structure and values

Immutable_Traffic_Light : Mutable_Variant_Record (Option => Yellow); -- this record is immutable, the discriminant cannot be changed
                                                                     -- even though the type declaration allows for mutable objects
                                                                     -- with different discriminant values
...
Mutable_Traffic_Light   := (Option => Yellow,                        -- mutation requires assignment of all components
                            Location => 54,                          -- for the given variant structure
                            Timeout => 2.3);
...
-- restrictions on objects, causing them to be immutable
type Traffic_Light_Access is access Mutable_Variant_Record;
Any_Traffic_Light       : Traffic_Light_Access :=
                           new Mutable_Variant_Record;
Aliased_Traffic_Light   : aliased Mutable_Variant_Record;


Conversely, you can declare record types so that the discriminant along with the structure of the variant record may not be changed. To make a record type declaration immutable, the discriminant must not have a default value.

 type Traffic_Light is (Red, Yellow, Green);

 type Immutable_Variant_Record (Option : Traffic_Light) is -- no default value makes the record type immutable
    record
       -- common components
       Location : Natural := 0;
       case Option is
          when Red =>
             -- components for red
             Flashing : Boolean := True;
          when Yellow =>
             -- components for yellow
             Timeout    : Duration;
          when Green =>
             -- components for green
             Whatever : Positive := 1;
       end case;
    end record;
...
Default_Traffic_Light   : Immutable_Variant_Record;                    -- ILLEGAL!
Immutable_Traffic_Light : Immutable_Variant_Record (Option => Yellow); -- this record is immutable, since the type declaration is immutable

Union

This language feature is only available from Ada 2005 on.

 type Traffic_Light is (Red, Yellow, Green);

 type Union (Option : Traffic_Light := Traffic_Light'First) is 
    record
       -- common components
       
       case Option is
          when Red =>
             -- components for red
          when Yellow =>
             -- components for yellow
          when Green =>
             -- components for green
       end case;
    end record;

 pragma Unchecked_Union (Union);
 pragma Convention (C, Union);    -- optional

The difference to a variant record is such that Option is not actually stored inside the record and never checked for correctness - it's just a dummy.

This kind of record is usually used for interfacing with C but can be used for other purposes as well (then without pragma Convention (C, Union);).

Tagged record

The tagged record is one part of what in other languages is called a class. It is the basic foundation of object orientated programming in Ada. The other two parts a class in Ada needs is a package and primitive operations.

type Person is tagged 
   record
      Name   : String (1 .. 10);
      Gender : Gender_Type;
   end record;
type Programmer is new Person with
   record
      Skilled_In : Language_List;
   end record;

Ada 2005 only:

type Programmer is new Person 
                   and Printable 
with 
   record
      Skilled_In : Language_List;
   end record;

Abstract tagged record

An abstract type has at least one abstract primitive operation, i.e. one of its operations is not defined and implementation must be provided by derivatives of the abstract type.

With aliased elements

If you come from C/C++, you are probably used to the fact that every element of a record - which is not part of a bitset - has an address. In Ada, this is not true because records, just like arrays, can be packed. And just like arrays you can use aliased to ensure that an element can be accessed via an access type.

type Basic_Record is 
   record
      A : aliased Integer;
   end record ;

Please note: each element needs its own aliased.

Limited Records

In addition to being variant, tagged, and abstract, records may also be limited (no assignment, and no predefined equality operation for Limited Types). In object oriented programming, when tagged objects are handled by references instead of copying them, this blends well with making objects limited.

See also

Wikibook

Ada Reference Manual

Ada 95

Ada 2005

Ada Issues


Access types

What's an Access Type?

Access types in Ada are what other languages call pointers. They point to objects located at certain addresses. So normally one can think of access types as simple addresses (there are exceptions from this simplified view). Ada instead of saying points to talks of granting access to or designating an object.

Objects of access types are implicitly initialized with null, i.e. they point to nothing when not explicitly initialized.

Access types should be used rarely in Ada. In a lot of circumstances where pointers are used in other languages, there are other ways without pointers. If you need dynamic data structures, first check whether you can use the Ada Container library. Especially for indefinite record or array components, the Ada 2012 package Ada.Containers.Indefinite_Holders (RM A.18.18 [Annotated]) can be used instead of pointers.

There are four kinds of access types in Ada: Pool access types - General access types - Anonymous access types - Access to subprogram types.

Pool access

A pool access type handles accesses to objects which were created on some specific heap (or storage pool as it is called in Ada). A pointer of these types cannot point to a stack or library level (static) object or an object in a different storage pool. Therefore, conversion between pool access types is illegal. (Unchecked_Conversion may be used, but note that deallocation via an access object with a storage pool different from the one it was allocated with is erroneous.)

type Person is record
  First_Name : String (1..30);
  Last_Name  : String (1..20);
end record;

type Person_Access is access Person;

A storage size clause may be used to limit the corresponding (implementation defined anonymous) storage pool. A storage size clause of 0 disables calls of an allocator.

for Person_Access'Storage_Size use 0;

The storage pool is implementation defined if not specified. Ada supports user defined storage pools, so you can define the storage pool with

for Person_Access'Storage_Pool use Pool_Name;

Objects in a storage pool are created with the keyword new:

Father: Person_Access := new Person;                                          -- uninitialized
Mother: Person_Access := new Person'(Mothers_First_Name, Mothers_Last_Name);  -- initialized

You access the object in the storage pool by appending .all. Mother.all is the complete record; components are denoted as usual with the dot notation: Mother.all.First_Name. When accessing components, implicit dereferencing (i.e. omitting all) can serve as a convenient shorthand:

Mother.all := (Last_Name => Father.Last_Name, First_Name => Mother.First_Name);  -- marriage

Implicit dereferencing also applies to arrays:

  type Vector is array (1 .. 3) of Complex;
  type Vector_Access is access Vector;

  VA: Vector_Access := new Vector;
  VB: array (1 .. 3) of Vector_Access := (others => new Vector);

  C1: Complex := VA (3);    -- a shorter equivalent for VA   .all (3)
  C2: Complex := VB (3)(1); -- a shorter equivalent for VB(3).all (1)

Be careful to discriminate between deep and shallow copies when copying with access objects:

Obj1.all := Obj2.all;  -- Deep copy: Obj1 still refers to an object different from Obj2, but it has the same content
Obj1 := Obj2;          -- Shallow copy: Obj1 now refers to the same object as Obj2

Deleting objects from a storage pool

Although the Ada standard mentions a garbage collector, which would automatically remove all unneeded objects that have been created on the heap (when no storage pool has been defined), only Ada compilers targeting a virtual machine like Java or .NET actually have garbage collectors.

When an access type goes out of scope, the corresponding still allocated data items are finalized (i.e. they do no longer exist) in an arbitrary order; the allocated memory, however, is only freed when the attribute Storage_Size has been defined for the access type via an attribute_definition clause. (Note: Finalization and deallocation are different things!)

Proof

The following are excerpts from the Ada Reference Manual. The ellipses stand for parts not relevant for the case.

RM 3.10(7/1) There are ... access-to-object types, whose values designate objects... Associated with an access-to-object type is a storage pool; several access types may share the same storage pool. ... A storage pool is an area of storage used to hold dynamically allocated objects (called pool elements) created by allocators.

(8) Access-to-object types are further subdivided into pool-specific access types, whose values can designate only the elements of their associated storage pool...

RM 7.6(1) ... Every object is finalized before being destroyed (for example, by leaving a subprogram_body containing an object_declaration, or by a call to an instance of Unchecked_Deallocation)...

RM 7.6.1(5) For the finalization of an object:

(6/3) If the full type of the object is an elementary type, finalization has no effect;

(7/3) If the full type of the object is a tagged type, and the tag of the object identifies a controlled type, the Finalize procedure of that controlled type is called;

(10) Immediately before an instance of Unchecked_Deallocation reclaims the storage of an object, the object is finalized. If an instance of Unchecked_Deallocation is never applied to an object created by an allocator, the object will still exist when the corresponding master completes, and it will be finalized then.

(11.1/3) Each nonderived access type T has an associated collection, which is the set of objects created by allocators of T, or of types derived from T. Unchecked_Deallocation removes an object from its collection. Finalization of a collection consists of finalization of each object in the collection, in an arbitrary order…

RM 13.11(1) Each access-to-object type has an associated storage pool. The storage allocated by an allocator comes from the pool; instances of Unchecked_Deallocation return storage to the pool. Several access types can share the same pool.

(2/2) A storage pool is a variable of a type in the class rooted at Root_Storage_Pool, which is an abstract limited controlled type. By default, the implementation chooses a standard storage pool for each access-to-object type…

(11) A storage pool type (or pool type) is a descendant of Root_Storage_Pool. The elements of a storage pool are the objects allocated in the pool by allocators.

(15) Storage_Size or Storage_Pool may be specified for a nonderived access-to-object type via an attribute_definition_clause...

(17) If Storage_Pool is not specified for a type defined by an access_to_object_definition, then the implementation chooses a standard storage pool for it in an implementation-defined manner...

(18/4) If Storage_Size is specified for an access type T, an implementation-defined pool P is used for the type. The Storage_Size of P is at least that requested, and the storage for P is reclaimed when the master containing the declaration of the access type is left...

Example

The following program will compile but will fail the accessibility check on runtime with an exception.

with Ada.Text_IO;
use Ada.Text_IO;

procedure Main is
   function Accessibility_Check_Fail
     return access String
   is
      -- Declare a new access type locally.
      -- All memory with this type will be finalized but not freed
      -- when the this type goes out of scope.
      type A_Type is access String;  -- no Storage_Size defined
      
      X : A_Type := new String'("x");  -- storage will be lost
      Y : access String;  -- defined locally
   begin
      Y := X;  -- data defined in a local pool will be finalized when function returns
      return Y;  -- exception should be raised
   end Accessibility_Check_Fail;
begin
   -- Accessibility check will fail because the accessiblity level associated
   -- with Y is deeper than the accessibility level of this scope.
   Put_Line(Accessibility_Check_Fail.all);
end Main;

There is also a pragma Controlled, which, when applied to such an access type, prevents automatic garbage collection of objects created with it. Note that pragma Controlled was dropped from Ada 2012, subpools for storage management replacing it. See RM 2012 13.11.3 [Annotated] and 13.11.4 [Annotated].

Therefore, in order to delete an object from the heap, you need the generic unit Ada.Unchecked_Deallocation. Apply utmost care to not create dangling pointers when deallocating objects as is shown in the example below. (And note that deallocating objects with a different access type than the one with which they were created is erroneous when the corresponding storage pools are different.)

with Ada.Unchecked_Deallocation;

procedure Deallocation_Sample is

   type Vector     is array (Integer range <>) of Float;
   type Vector_Ref is access Vector;

   procedure Free_Vector is new Ada.Unchecked_Deallocation
      (Object => Vector, Name => Vector_Ref);
  
   VA, VB: Vector_Ref;
   V     : Vector;

begin

   VA     := new Vector (1 .. 10);
   VB     := VA;  -- points to the same location as VA

   VA.all := (others => 0.0);

   --  ... Do whatever you need to do with the vector

   Free_Vector (VA); -- The memory is deallocated and VA is now null

   V := VB.all;  -- VB is not null, access to a dangling pointer is erroneous

end Deallocation_Sample;

It is exactly because of this problem with dangling pointers that the deallocation operation is called unchecked. It is the chore of the programmer to take care that this does not happen.

Since Ada allows for user-defined storage pools, you could also try a garbage collector library.

Constructing Reference Counting Pointers

You can find some implementations of reference counting pointers, called Safe or Smart Pointers, on the net. Using such a type prevents caring about deallocation, since this will automatically be done when there are no more pointers to an object. But be careful - most of those implementations do not prevent deliberate deallocation, thus undermining the alleged safety attained with their use.

A nice tutorial how to construct such a type can be found in a series of Gems on the AdaCore web site.

Gem #97: Reference Counting in Ada – Part 1 This little gem constructs a simple reference counted pointer that does not prevent deallocation, i.e. is inherently unsafe.

Gem #107: Preventing Deallocation for Reference-counted Types This further gem describes how to arrive at a pointer type whose safety cannot be compromised (tasking issues aside). The cost of this improved safety is awkward syntax.

Gem #123: Implicit Dereferencing in Ada 2012 This gem shows how to simplify the syntax with the new Ada 2012 generation. (Admittedly, this gem is a bit unrelated to reference counting since the new language feature can be applied to any kind of container.)

General access

General access types grant access to objects created on any storage pool, on the stack or at library level (static). They come in two versions, granting either read-write access or read-only access. Conversions between general access types are allowed, but subject to certain access level checks.

Dereferencing is like for pool access types. Objects (other than pool objects) to be referenced have to be declared aliased, and references to them are created with the attribute 'Access. Access level restrictions prevent accesses to objects from outliving the accessed object, which would make the program erroneous. The attribute 'Unchecked_Access omits the corresponding checks.

Access to Variable

When the keyword all is used in their definition, they grant read-write access.

type Day_Of_Month is range 1 .. 31;            
type Day_Of_Month_Access is access all Day_Of_Month;

Access to Constant

General access types granting read-only access to the referenced object use the keyword constant in their definition. The referenced object may be a constant or a variable.

type Day_Of_Month is range 1 .. 31;            
type Day_Of_Month_Access is access constant Day_Of_Month;

Some examples

 type General_Pointer  is access all      Integer;
 type Constant_Pointer is access constant Integer;

 I1: aliased constant Integer := 10;
 I2: aliased Integer;

 P1: General_Pointer  := I1'Access;  -- illegal
 P2: Constant_Pointer := I1'Access;  -- OK, read only
 P3: General_Pointer  := I2'Access;  -- OK, read and write
 P4: Constant_Pointer := I2'Access;  -- OK, read only

 P5: constant General_Pointer := I2'Access;  -- read and write only to I2

Anonymous access

Also Anonymous access types come in two versions like general access types, granting either read-write access or read-only access depending on whether the keyword constant appears.

An anonymous access can be used as a parameter to a subprogram or as a discriminant. Here are some examples:

procedure Modify (Some_Day: access          Day_Of_Month);
procedure Test   (Some_Day: access constant Day_Of_Month);  -- Ada 2005 only
task type Thread (Execute_For_Day: access Day_Of_Month) is
   ...
end Thread;
type Day_Data (Store_For_Day: access Day_Of_Month) is record
  -- components
end record;

Before using an anonymous access, you should consider a named access type or, even better, consider if the "out" or "in out" modifier is not more appropriate.

This language feature is only available from Ada 2005 on.

In Ada 2005, anonymous accesses are allowed in more circumstances:

type Object is record
  M   : Integer;
  Next: access Object;
end record;

X: access Integer;

function F return access constant Float;

Implicit Dereference

This language feature has been introduced in Ada 2012.

Ada 2012 simplifies accesses to objects via pointers with new syntax.

Imagine you have a container holding some kind of elements.

type Container   is private;
type Element_Ptr is access Element;

procedure Put (X: Element; Into: in out Container);

Now, how do you access elements stored in the container. Of course, you can retrieve them by

function Get (From: Container) return Element;

This will however copy the element, which is unfortunate if the element is big. You get direct access with

function Get (From: Container) return Element_Ptr;

Now, pointers are dangerous since you might easily create dangling pointers like so:

P: Element_Ptr := Get (Cont);
P.all := E;
Free (P);
... Get (Cont) -- this is now a dangling pointer

Use of an accessor object instead of an access type can prevent inadvertent deallocation (this is still Ada 2005):

type Accessor (Data: not null access Element) is limited private;  -- read/write access
function Get (From: Container) return Accessor;

(For the null exclusion not null in the declaration of the discriminant, see below). Access via such an accessor is safe: The discriminant can only be used for dereferencing, it cannot be copied to an object of type Element_Ptr because its accessibility level is deeper. In the form above, the accessor provides read and write access. If the keyword constant is added, only read access is possible.

type Accessor (Data: not null access constant Element) is limited private;  -- only read access

Access to the container object now looks like so:

Get (Cont).all      := E;  -- via access type: dangerous
Get (Cont).Data.all := E;  -- via accessor: safe, but ugly

Here the new Ada 2012 feature of aspects comes along handy; for the case at hand, the aspect Implicit_Dereference is the one we need:

type Accessor (Data: not null access Element) is limited private
   with Implicit_Dereference => Data;

Now rather than writing the long and ugly function call of above, we can just omit the discriminant and its dereference like so:

Get (Cont).Data.all := E;  -- Ada 2005 via accessor: safe, but ugly
Get (Cont)          := E;  -- Ada 2012 implicit dereference

Note that the call Get (Cont) is overloaded — it can denote the accessor object or the element, the compiler will select the correct interpretation depending on context.

Null exclusions

This language feature is only available from Ada 2005 on.

All access subtypes can be modified with not null, objects of such a subtype can then never have the value null, so initializations are compulsory.

type    Day_Of_Month_Access          is access   Day_Of_Month;
subtype Day_Of_Month_Not_Null_Access is not null Day_Of_Month_Access;

The language also allows to declare the first subtype directly with a null exclusion:

type Day_Of_Month_Access is not null access Day_Of_Month;

However, in nearly all cases this is not a good idea because it renders objects of this type nearly unusable (for example, you are unable to free the allocated memory). Not null accesses are intended for access subtypes, object declarations, and subprogram parameters.[7]

Access to Subprogram

An access to subprogram allows the caller to call a subprogram without knowing its name nor its declaration location. One of the uses of this kind of access is the well known callbacks.

type Callback_Procedure is access procedure (Id  : Integer;
                                             Text: String);

type Callback_Function is access function (The_Alarm: Alarm) return Natural;

For getting an access to a subprogram, the attribute Access is applied to a subprogram name with the proper parameter and result profile.

procedure Process_Event (Id  : Integer;
                         Text: String);

My_Callback: Callback_Procedure := Process_Event'Access;

Anonymous access to Subprogram

This language feature is only available from Ada 2005 on.

procedure Test (Call_Back: access procedure (Id: Integer; Text: String));

There is now no limit on the number of keyword in a sequence:

function F return access function return access function return access Some_Type;

This is a function that returns the access to a function that in turn returns an access to a function returning an access to some type.

Access FAQ

A few "Frequently Asked Question" and "Frequently Encountered Problems" (mostly from C users) regarding Ada's access types.

Access vs. access all

An access all can do anything a simple access can do. So one might ask: "Why use simple access at all?" - And indeed some programmers never use simple access.

Unchecked_Deallocation is always dangerous if misused. It is just as easy to deallocate a pool-specific object twice, and just as dangerous as deallocating a stack object. The advantage of "access all" is that you may not need to use Unchecked_Deallocation at all.

Moral: if you have (or may have) a valid reason to store an 'Access or 'Unchecked_Access into an access object, then use "access all" and don't worry about it. If not, the mantra of "least privilege" suggests that the "all" should be left out (don't enable capabilities that you are not going to use).

The following (perhaps disastrous) example will try to deallocate a stack object:

declare

  type Day_Of_Month is range 1 .. 31;            
  type Day_Of_Month_Access is access all Day_Of_Month;

  procedure Free is new Ada.Unchecked_Deallocation
      (Object => Day_Of_Month,
       Name   => Day_Of_Month_Access);

  A  : aliased Day_Of_Month;
  Ptr: Day_Of_Month_Access := A'Access;

begin

   Free(Ptr);

end;

With a simple access you know at least that you won't try to deallocate a stack object. The reason is that access does not allow pointers to be created from stack objects.

Access vs. System.Address

An access can be something different from a mere memory address, it may be something more. For example, an "access to String" often needs some way of storing the string size as well. If you need a simple address and are not concerned about strong typing, use the System.Address type.

C compatible pointer

The correct way to create a C compatible access is to use pragma Convention:

type Day_Of_Month is range 1 .. 31;
for  Day_Of_Month'Size use Interfaces.C.int'Size;

pragma Convention (Convention => C,
                   Entity     => Day_Of_Month);

type Day_Of_Month_Access is access Day_Of_Month;

pragma Convention (Convention => C,
                   Entity     => Day_Of_Month_Access);

pragma Convention should be used on any type you want to use in C. The compiler will warn you if the type cannot be made C compatible.

You may also consider the following - shorter - alternative when declaring Day_Of_Month:

type Day_Of_Month is new Interfaces.C.int range 1 .. 31;

Before you use access types in C, you should consider using the normal "in", "out" and "in out" modifiers. pragma Export and pragma Import know how parameters are usually passed in C and will use a pointer to pass a parameter automatically where C would have used them as well. Of course the RM contains precise rules on when to use a pointer for "in", "out", and "in out" - see "B.3: Interfacing with C [Annotated]".

Where is void*?

While actually a problem for "interfacing with C", here are some possible solutions:

procedure Test is

  subtype Pvoid is System.Address;

  -- the declaration in C looks like this:
  -- int C_fun(int *)
  function C_fun (pv: Pvoid) return Integer;
  pragma Import (Convention    => C,
                 Entity        => C_fun,     -- any Ada name
                 External_Name => "C_fun");  -- the C name

  Pointer: Pvoid;

  Input_Parameter: aliased Integer := 32;
  Return_Value   : Integer;

begin

  Pointer      := Input_Parameter'Address;
  Return_Value := C_fun (Pointer);

end Test;

Less portable, but perhaps more usable (for 32 bit CPUs):

type void is mod 2 ** 32;
for void'Size use 32;

With GNAT you can get 32/64 bit portability by using:

type void is mod System.Memory_Size;
for void'Size use System.Word_Size;

Closer to the true nature of void - pointing to an element of zero size is a pointer to a null record. This also has the advantage of having a representation for void and void*:

type Void is null record;
pragma Convention (C, Void);

type Void_Ptr is access all Void;
pragma Convention (C, Void_Ptr);

Thin and Fat Access Types

The difference between an access type and an address will be detailed in the following. The term pointer is used because this is usual terminology.

There is a predefined unit System.Address_to_Access_Conversion converting back and forth between access values and addresses. Use these conversions with care, as is explained below.

Thin Pointers

Thin pointers grant access to constrained subtypes.

type Int     is range -100 .. +500;
type Acc_Int is access Int;

type Arr     is array (1 .. 80) of Character;
type Acc_Arr is access Arr;

Objects of subtypes like these have a static size, so a simple address suffices to access them. In the case of arrays, this is generally the address of the first element.

For pointers of this kind, use of System.Address_to_Access_Conversion is safe.

Fat Pointers

type Unc     is array (Integer range <>) of Character;
type Acc_Unc is access Unc;

Objects of subtype Unc need a constraint, i.e. a start and a stop index, thus pointers to them need also to include those. So a simple address like the one of the first component is not sufficient. Note that A'Address is the same as A(A'First)'Address for any array object.

For pointers of this kind, System.Address_to_Access_Conversion will probably not work satisfactorily.

Example

CO: aliased Unc (-1 .. +1) := (-1 .. +1 => ' ');
UO: aliased Unc            := (-1 .. +1 => ' ');

Here, CO is a nominally constrained object, a pointer to it need not store the constraint, i.e. a thin pointer suffices. In contrast, UO is an object of a nominally unconstrained subtype, its actual subtype is constrained by the initial value.

A: Acc_Unc            := CO'Access;  -- illegal
B: Acc_Unc            := UO'Access;  -- OK
C: Acc_Unc (CO'Range) := CO'Access;  -- also illegal

The relevant paragraphs in the RM are difficult to understand. In short words:

An access type's target type is called the designated subtype, in our example Unc. RM 3.10.2 [Annotated](27.1/2) requires that Acc_Unc's designated subtype statically match the nominal subtype of the object.

Now the nominal subtype of CO is the constrained anonymous subtype Unc (-1 .. +1), the nominal subtype of UO is the unconstrained subtype Unc. In the illegal cases, the designated and nominal subtypes do not statically match.

See also

Wikibook

Ada Reference Manual

Ada 95

Ada 2005

Newest RM

Ada Quality and Style Guide

References


Limited types

Limited Types

When a type is declared limited this means that objects of the type cannot be assigned values of the same type. An Object b of limited type LT cannot be copied into an object a of same type LT.

Additionally, there is no predefined equality operation for objects of a limited type.

The desired effects of declaring a type limited include prevention of shallow copying. Also, the (unique) identity of an object is retained: once declared, a name of a variable of type LT will continue to refer to the same object.

The following example will use a rather simplifying type Boat.

     type Boat is limited private;

     function Choose
       (Load  : Sailors_Units;
        Speed : Sailors_Units)
        return  Boat;

     procedure Set_Sail (The_Boat : in out Boat);

When we declare a variable to be of type Boat, its name will denote one boat from then on. Boats will not be copied into one another.

The full view of a boat might be implemented as a record such as

     type Boat is limited record
        Max_Sail_Area : Sailors_Units;
        Max_Freight   : Sailors_Units;
        Sail_Area     : Sailors_Units;
        Freight       : Sailors_Units;
     end record;

The Choose function returns a Boat object depending on the parameters Load and Speed. If we now declare a variable of type Boat we will be better off Choosing an initial Boat (or else we might be dropping into uninitialized waters!). But when we do so, the initialization looks suspiciously like assignment which is not available with limited types:

  procedure Travel (People : Positive; Average_Speed : Sailors_Units) is

     Henrietta : Boat :=   -- assignment?
        Choose
          (Load  => People * Average_Weight * 1.5,
           Speed => Average_Speed * 1.5);

  begin
     Set_Sail (Henrietta);
  end Travel;

Fortunately, current Ada distinguishes initialization from copying. Objects of a limited type may be initialized by an initialization expression on the right of the delimiter :=.

(Just to prevent confusion: The Ada Reference Manual discriminates between assignment and assignment statement, where assignment is part of the assignment statement. An initialisation is of course an assignment which, for limited types, is done in place. An assignment statement involves copying, which is forbidden for limited types.)

Related to this feature are aggregates of limited types and “constructor functions” for limited types. Internally, the implementation of the Choose function will return a limited record. However, since the return type Boat is limited, there must be no copying anywhere. Will this work? A first attempt might be to declare a result variable local to Choose, manipulate result, and return it. The result object needs to be “transported” into the calling environment. But result is a variable local to Choose. When Choose returns, result will no longer be in scope. Therefore it looks like result must be copied but this is not permitted for limited types. There are two solutions provided by the language: extended return statements (see 6.5: Return Statements [Annotated]) and aggregates of limited types. The following body of Choose returns an aggregate of limited type Boat, after finding the initial values for its components.

     function Choose
       (Load  : Sailors_Units;
        Speed : Sailors_Units)
        return  Boat
     is
        Capacity : constant Sailors_Units := Capacity_Needed (Load);
     begin
        return Boat'
          (Max_Freight   => Capacity,
           Max_Sail_Area => Sail_Needed (Capacity),
           Freight       => Load,
           Sail_Area     => 0.0);
     end Choose;

The object that is returned is at the same time the object that is to have the returned value. The function therefore initializes Henrietta in place.

In parallel to the predefined type Ada.Finalization.Controlled, Ada provides the type Limited_Controlled in the same package. It is a limited version of the former.

Initialising Limited Types

A few methods to initialise such types are presented.

package Limited_Private_Samples is

  type Uninitialised  is limited private;
  type Preinitialised is limited private;

  type Dynamic_Initialisation is limited private;
  function Constructor (X: Integer)  -- any kind of parameters
    return Dynamic_Initialisation;

  type Needs_Constructor (<>) is limited private;
  function Constructor (X: Integer)  -- any kind of parameters
    return Needs_Constructor;

private

  type Uninitialised is record
    I: Integer;
  end record;

  type Preinitialised is record
    I: Integer := 0;  -- can also be a function call
  end record;

  type Void is null record;
  function Constructor (Object: access Dynamic_Initialisation) return Void;

  type Dynamic_Initialisation is limited record
    Hook: Void := Constructor (Dynamic_Initialisation'Access);
    Bla : Integer;  -- any needed components
  end record;

  type Needs_Constructor is record
    I: Integer;
  end record;

end Limited_Private_Samples;
package body Limited_Private_Samples is

  function Constructor (Object: access Dynamic_Initialisation) return Void is
  begin
    Object.Bla := 5;  -- may be any value only known at run time
    return (null record);
  end Constructor;

  function Constructor (X: Integer) return Dynamic_Initialisation is
  begin
    return (Hook => (null record),
            Bla  => 42);
  end Constructor;

  function Constructor (X: Integer) return Needs_Constructor is
  begin
    return (I => 42);
  end Constructor;

end Limited_Private_Samples;
 with Limited_Private_Samples;
 use  Limited_Private_Samples;
 
 procedure Try is
 
   U: Uninitialised;   -- very bad
   P: Preinitialised;  -- has initial value (good)
  
   D1: Dynamic_Initialisation;  -- has initial value (good)
   D2: Dynamic_Initialisation := Constructor (0);  -- Ada 2005 initialisation
   D3: Dynamic_Initialisation renames Constructor (0);  -- already Ada 95
 
   -- I: Needs_Constructor;  -- Illegal without initialisation
   N: Needs_Constructor := Constructor (0);  -- Ada 2005 initialisation
 
 begin
 
   null;
 
 end Try;

Note that D3 is a constant, whereas all others are variables.

Also note that the initial value that is defined for the component of Preinitialised is evaluated at the time of object creation, i.e. if an expression is used instead of the literal, the value can be run-time dependent.

X, Y: Preinitialised;

In this declaration of two objects, the initial expression will be evaluated twice and can deliver different values, because it is equivalent to the sequence:[1]

X: Preinitialised;
Y: Preinitialised;

So X is initialised before Y.

See also

Ada 95 Reference Manual

Ada 2005 Reference Manual

Ada Quality and Style Guide

References

  1. ISO/IEC 8652:2007. "3.3.1 Object Declarations (7)". Ada 2005 Reference Manual. Any declaration [...] with more than one defining_identifier is equivalent to a series of declarations each containing one defining_identifier from the list, [...] in the same order as the list. {{cite book}}: Unknown parameter |chapterurl= ignored (|chapter-url= suggested) (help)


Strings

Ada supports three different types of strings. Each string type is designed to solve a different problem.

In addition, every string type is implemented for each available Characters type (Character, Wide_Character, Wide_Wide_Character) giving a complement of nine combinations.

Fixed-length string handling

Fixed-Length Strings (the predefined type String) are arrays of Character, and consequently of a fixed length. Since String is an indefinite subtype the length does not need to be known at compile time — the length may well be calculated at run time. In the following example the length is calculated from command-line argument 1:

X : String := Ada.Command_Line.Argument (1);

However once the length has been calculated and the string has been created the length stays constant. Try the following program which shows a typical mistake:

File: show_commandline_1.adb (view, plain text, download page, browse all)
with Ada.Text_IO;
with Ada.Command_Line;

procedure Show_Commandline_1 is

   package T_IO renames Ada.Text_IO;
   package CL   renames Ada.Command_Line;

   X : String := CL.Argument (1);

begin
   T_IO.Put ("Argument 1 = ");
   T_IO.Put_Line (X);

   X := CL.Argument (2);

   T_IO.Put ("Argument 2 = ");
   T_IO.Put_Line (X);
end Show_Commandline_1;

The program will only work when the 1st and 2nd parameter have the same length. This is even true when the 2nd parameter is shorter. There is neither an automatic padding of shorter strings nor an automatic truncation of longer strings.

Having said that, the package Ada.Strings.Fixed contains a set of procedures and functions for Fixed-Length String Handling which allows padding of shorter strings and truncation of longer strings.

Try the following example to see how it works:

File: show_commandline_2.adb (view, plain text, download page, browse all)
with Ada.Text_IO;
with Ada.Command_Line;
with Ada.Strings.Fixed;

procedure Show_Commandline_2 is

   package T_IO renames Ada.Text_IO;
   package CL   renames Ada.Command_Line;
   package S    renames Ada.Strings;
   package SF   renames Ada.Strings.Fixed;

   X : String := CL.Argument (1);

begin
   T_IO.Put ("Argument 1 = ");
   T_IO.Put_Line (X);

   SF.Move (
     Source  => CL.Argument (2),
     Target  => X,
     Drop    => S.Right,
     Justify => S.Left,
     Pad     => S.Space);

   T_IO.Put ("Argument 2 = ");
   T_IO.Put_Line (X);
end Show_Commandline_2;

Bounded-length string handling

Bounded-Length Strings can be used when the maximum length of a string is known and/or restricted. This is often the case in database applications where only a limited number of characters can be stored.

Like Fixed-Length Strings the maximum length does not need to be known at compile time — it can also be calculated at runtime — as the example below shows:

File: show_commandline_3.adb (view, plain text, download page, browse all)
with Ada.Text_IO;
with Ada.Command_Line;
with Ada.Strings.Bounded;

procedure Show_Commandline_3 is

   package T_IO renames Ada.Text_IO;
   package CL   renames Ada.Command_Line;

   function Max_Length (
      Value_1 : Integer;
      Value_2 : Integer)
   return
      Integer
   is
      Retval : Integer;
   begin
      if Value_1 > Value_2 then
         Retval := Value_1;
      else
         Retval := Value_2;
      end if;
      return Retval;
   end Max_Length;

   pragma Inline (Max_Length);

   package SB
   is new Ada.Strings.Bounded.Generic_Bounded_Length (
       Max => Max_Length (
                  Value_1 => CL.Argument (1)'Length,
                  Value_2 => CL.Argument (2)'Length));

   X :  SB.Bounded_String
     := SB.To_Bounded_String (CL.Argument (1));

begin
   T_IO.Put ("Argument 1 = ");
   T_IO.Put_Line (SB.To_String (X));

   X := SB.To_Bounded_String (CL.Argument (2));

   T_IO.Put ("Argument 2 = ");
   T_IO.Put_Line (SB.To_String (X));
end Show_Commandline_3;

You should know that Bounded-Length Strings have some distinct disadvantages. Most noticeable is that each Bounded-Length String is a different type which makes converting them rather cumbersome. Also a Bounded-Length String type always allocates memory for the maximum permitted string length for the type. The memory allocation for a Bounded-Length String is equal to the maximum number of string "characters" plus an implementation dependent number containing the string length (each character can require allocation of more than one byte per character, depending on the underlying character type of the string, and the length number is 4 bytes long for the Windows GNAT Ada compiler v3.15p, for example).

Unbounded-length string handling

Last but not least there is the Unbounded-Length String. In fact: If you are not doing embedded or database programming this will be the string type you are going to use most often as it gives you the maximum amount of flexibility.

As the name suggest the Unbounded-Length String can hold strings of almost any length — limited only to the value of Integer'Last or your available heap memory. This is because Unbounded_String type is implemented using dynamic memory allocation behind the scenes, providing lower efficiency but maximum flexibility.

File: show_commandline_4.adb (view, plain text, download page, browse all)
with Ada.Text_IO;
with Ada.Command_Line;
with Ada.Strings.Unbounded;

procedure Show_Commandline_4 is

   package T_IO renames Ada.Text_IO;
   package CL   renames Ada.Command_Line;
   package SU   renames Ada.Strings.Unbounded;

   X :  SU.Unbounded_String 
     := SU.To_Unbounded_String (CL.Argument (1));

begin
   T_IO.Put ("Argument 1 = ");
   T_IO.Put_Line (SU.To_String (X));

   X := SU.To_Unbounded_String (CL.Argument (2));

   T_IO.Put ("Argument 2 = ");
   T_IO.Put_Line (SU.To_String (X));
end Show_Commandline_4;

As you can see the Unbounded-Length String example is also the shortest (disregarding the buggy first example) — this makes using Unbounded-Length Strings very appealing.

See also

Wikibook

Ada 95 Reference Manual

Ada 2005 Reference Manual


Subprograms

In Ada the subprograms are classified into two categories: procedures and functions. A procedures call is a statement and does not return any value, whereas a function returns a value and must therefore be a part of an expression.

Subprogram parameters may have three modes.

in
The actual parameter value goes into the call and is not changed there; the formal parameter is a constant and allows only reading – with a caveat, see Ada Programming/Constants. This is the default when no mode is given. The actual parameter can be an expression.
in out
The actual parameter goes into the call and may be redefined. The formal parameter is a variable and can be read and written.
out
The actual parameter's value before the call is irrelevant, it will get a value in the call. The formal parameter can be read and written. (In Ada 83 out parameters were write-only.)

A parameter of any mode may also be explicitly aliased.

access
The formal parameter is an access (a pointer) to some variable. (This is not a parameter mode from the reference manual point of view.)

Note that parameter modes do not specify the parameter passing method. Their purpose is to document the data flow.

The parameter passing method depends on the type of the parameter. A rule of thumb is that parameters fitting into a register are passed by copy, others are passed by reference. For certain types, there are special rules, for others the parameter passing mode is left to the compiler (which you can assume to do what is most sensible). Tagged types are always passed by reference.

Explicitly aliased parameters and access parameters specify pass by reference.

Unlike in the C class of programming languages, Ada subprogram calls cannot have empty parameters parentheses ( ) when there are no parameters.

Procedures

A procedure call in Ada constitutes a statement by itself.

For example:

procedure A_Test (A, B: in Integer; C: out Integer) is
begin
   C := A + B;
end A_Test;

When the procedure is called with the statement

A_Test (5 + P, 48, Q);

the expressions 5 + P and 48 are evaluated (expressions are only allowed for in parameters), and then assigned to the formal parameters A and B, which behave like constants. Then, the value A + B is assigned to formal variable C, whose value will be assigned to the actual parameter Q when the procedure finishes.

C, being an out parameter, is an uninitialized variable before the first assignment. (Therefore in Ada 83, there existed the restriction that out parameters are write-only. If you wanted to read the value written, you had to declare a local variable, do all calculations with it, and finally assign it to C before return. This was awkward and error prone so the restriction was removed in Ada 95.)

Within a procedure, the return statement can be used without arguments to exit the procedure and return the control to the caller.

For example, to solve an equation of the kind  :

with Ada.Numerics.Elementary_Functions;
use  Ada.Numerics.Elementary_Functions;

procedure Quadratic_Equation
   (A, B, C :     Float;   -- By default it is "in".
    R1, R2  : out Float;
    Valid   : out Boolean)
is
   Z : Float;
begin
   Z := B**2 - 4.0 * A * C;
   if Z < 0.0 or A = 0.0 then
      Valid := False;  -- Being out parameter, it should be modified at least once.
      R1    := 0.0;
      R2    := 0.0;
   else
      Valid := True;
      R1    := (-B + Sqrt (Z)) / (2.0 * A);
      R2    := (-B - Sqrt (Z)) / (2.0 * A);
   end if;
end Quadratic_Equation;

The function SQRT calculates the square root of non-negative values. If the roots are real, they are given back in R1 and R2, but if they are complex or the equation degenerates (A = 0), the execution of the procedure finishes after assigning to the Valid variable the False value, so that it is controlled after the call to the procedure. Notice that the out parameters should be modified at least once, and that if a mode is not specified, it is implied in.

Functions

A function is a subprogram that can be invoked as part of an expression. Until Ada 2005, functions can only take in (the default) or access parameters; the latter can be used as a work-around for the restriction that functions may not have out parameters. Ada 2012 has removed this restriction.

Here is an example of a function body:

function Minimum (A, B: Integer) return Integer is
begin
   if A <= B then
      return A;
   else
      return B;
   end if;
end Minimum;

(There is, by the way, also the attribute Integer'Min, see RM 3.5.) Or in Ada2012:

function Minimum (A, B: Integer) return Integer is
begin
   return (if A <= B then A else B);
end Minimum;

or even shorter as an expression function

function Minimum (A, B: Integer) return Integer is (if A <= B then A else B);

The formal parameters with mode in behave as local constants whose values are provided by the corresponding actual parameters. The statement return is used to indicate the value returned by the function call and to give back the control to the expression that called the function. The expression of the return statement may be of arbitrary complexity and must be of the same type declared in the specification. If an incompatible type is used, the compiler gives an error. If the restrictions of a subtype are not fulfilled, e.g. a range, it raises a Constraint_Error exception.

The body of the function can contain several return statements and the execution of any of them will finish the function, returning control to the caller. If the flow of control within the function branches in several ways, it is necessary to make sure that each one of them is finished with a return statement. If at run time the end of a function is reached without encountering a return statement, the exception Program_Error is raised. Therefore, the body of a function must have at least one such return statement.

Every call to a function produces a new copy of any object declared within it. When the function finalizes, its objects disappear. Therefore, it is possible to call the function recursively. For example, consider this implementation of the factorial function:

function Factorial (N : Positive) return Positive is
begin
   if N = 1 then
      return 1;
   else
      return (N * Factorial (N - 1));
   end if;
end Factorial;

When evaluating the expression Factorial (4); the function will be called with parameter 4 and within the function it will try to evaluate the expression Factorial (3), calling itself as a function, but in this case parameter N would be 3 (each call copies the parameters) and so on until N = 1 is evaluated which will finalize the recursion and then the expression will begin to be completed in the reverse order.

A formal parameter of a function can be of any type, including vectors or records. Nevertheless, it cannot be an anonymous type, that is, its type must be declared before, for example:

type Float_Vector is array (Positive range <>) of Float;

function Add_Components (V: Float_Vector) return Float is
   Result : Float := 0.0;
begin
   for I in V'Range loop
      Result := Result + V(I);
   end loop;
   return Result;
end Add_Components;

In this example, the function can be used on a vector of arbitrary dimension. Therefore, there are no static bounds in the parameters passed to the functions. For example, it is possible to be used in the following way:

V4  : Float_Vector (1 .. 4) := (1.2, 3.4, 5.6, 7.8);
Sum : Float;

Sum := Add_Components (V4);

In the same way, a function can also return a type whose bounds are not known a priori. For example:

function Invert_Components (V : Float_Vector) return Float_Vector is
   Result : Float_Vector(V'Range);   -- Fix the bounds of the vector to be returned.
begin
   for I in V'Range loop
      Result(I) := V (V'First + V'Last - I);
   end loop;
   return Result;
end Invert_Components; 

The variable Result has the same bounds as V, so the returned vector will always have the same dimension as the one passed as parameter.

A value returned by a function can be used without assigning it to a variable, it can be referenced as an expression. For example, Invert_Components (V4) (1), where the first element of the vector returned by the function would be obtained (in this case, the last element of V4, i.e. 7.8).

Named parameters

In subprogram calls, named parameter notation (i.e. the name of the formal parameter followed of the symbol => and then the actual parameter) allows the rearrangement of the parameters in the call. For example:

Quadratic_Equation (Valid => OK, A => 1.0, B => 2.0, C => 3.0, R1 => P, R2 => Q);
F := Factorial (N => (3 + I));

This is especially useful to make clear which parameter is which.

Phi := Arctan (A, B);
Phi := Arctan (Y => A, X => B);

The first call (from Ada.Numerics.Elementary_Functions) is not very clear. One might easily confuse the parameters. The second call makes the meaning clear without any ambiguity.

Another use is for calls with numeric literals:

Ada.Float_Text_IO.Put_Line (X, 3, 2, 0);  -- ?
Ada.Float_Text_IO.Put_Line (X, Fore => 3, Aft => 2, Exp => 0);  -- OK

Default parameters

On the other hand, formal parameters may have default values. They can, therefore, be omitted in the subprogram call. For example:

procedure By_Default_Example (A, B: in Integer := 0);

can be called in these ways:

By_Default_Example (5, 7);      -- A = 5, B = 7
By_Default_Example (5);         -- A = 5, B = 0
By_Default_Example;             -- A = 0, B = 0
By_Default_Example (B => 3);    -- A = 0, B = 3
By_Default_Example (1, B => 2); -- A = 1, B = 2

In the first statement, a "regular call" is used (with positional association); the second also uses positional association but omits the second parameter to use the default; in the third statement, all parameters are by default; the fourth statement uses named association to omit the first parameter; finally, the fifth statement uses mixed association, here the positional parameters have to precede the named ones.

Note that the default expression is evaluated once for each formal parameter that has no actual parameter. Thus, if in the above example a function would be used as defaults for A and B, the function would be evaluated once in case 2 and 4; twice in case 3, so A and B could have different values; in the others cases, it would not be evaluated.

Renaming

Subprograms may be renamed. The parameter and result profile for a renaming-as-declaration must be mode conformant.

procedure Solve
  (A, B, C: in  Float;
   R1, R2 : out Float;
   Valid  : out Boolean) renames Quadratic_Equation;

This may be especially comfortable for tagged types.

package Some_Package is
  type Message_Type is tagged null record;
  procedure Print (Message: in Message_Type);
end Some_Package; 
with Some_Package;
procedure Main is
  Message: Some_Package.Message_Type;
  procedure Print renames Message.Print;  -- this has convention intrinsic, see RM 6.3.1(10.1/2)
  Method_Ref: access procedure := Print'Access;  -- thus taking 'Access should be illegal; GNAT GPL 2012 allows this
begin  -- All these calls are equivalent:
  Some_Package.Print (Message);  -- traditional call without use clause
  Message.Print;                 -- Ada 2005 method.object call - note: no use clause necessary
  Print;                         -- Message.Print is a parameterless procedure and can be renamed as such
  Method_Ref.all;                -- GNAT GPL 2012 allows illegal call via an access to the renamed procedure Print
                                 -- This has been corrected in the current version (as of Nov 22, 2012)
end Main;

But note that Message.Print'Access; is illegal, you have to use a renaming declaration as above.

Since only mode conformance is required (and not full conformance as between specification and body), parameter names and default values may be changed with renamings:

procedure P (X: in Integer :=  0);
procedure R (A: in Integer := -1) renames P;

See also

Wikibook

Ada 95 Reference Manual

Ada 2005 Reference Manual

Ada Quality and Style Guide


Packages

Ada encourages the division of code into separate modules called packages. Each package can contain any combination of items.

Some of the benefits of using packages are:

  • package contents are placed in a separate namespace, preventing naming collisions,
  • implementation details of the package can be hidden from the programmer (information hiding),
  • object orientation requires defining a type and its primitive subprograms within a package, and
  • packages that are library units can be separately compiled.

Some of the more common package usages are:

  • a group of related subprograms along with their shared data, with the data not visible outside the package,
  • one or more data types along with subprograms for manipulating those data types, and
  • a generic package that can be instantiated under varying conditions.

The following is a quote from the current Ada Reference Manual Section 7: Packages. RM 7(1) [Annotated]

Packages are program units that allow the specification of groups of logically related entities. Typically, a package contains the declaration of a type (often a private type or private extension) along with the declaration of primitive subprograms of the type, which can be called from outside the package, while their inner workings remain hidden from outside users.

Separate compilation

Note: The following chapters deal with packages on library level. This is the most common use, since packages are the basic code structuring means in Ada. However, Ada being a block oriented language, packages can be declared at any level in any declarative region. In this case, the normal visibility rules apply, also for the package body.

Package specifications and bodies on library level are compilation units, so can be separately compiled. Ada has nothing to say about how and where compilation units are stored. This is implementation dependent. (Most implementations indeed store compilation units in files of their own; name suffixes vary, GNAT uses .ads and .adb, APEX .1.ada and .2.ada.) The package body can itself be divided into multiple parts by specifying that subprogram implementations or bodies of nested packages are separate. These are then further compilation units.

One of the biggest advantages of Ada over most other programming languages is its well-defined system of modularization and separate compilation. Even though Ada allows separate compilation, it maintains the strong type checking among the various compilations by enforcing rules of compilation order and compatibility checking. Ada compilers determine the compilation sequence; no make file is needed. Ada uses separate compilation (like Modula-2, Java and C#), and not independent compilation (as C/C++ does), in which the various parts are compiled with no knowledge of the other compilation units with which they will be combined.

A note to C/C++ users: Yes, you can use the preprocessor to emulate separate compilation — but it is only an emulation and the smallest mistake leads to very hard to find bugs. It is telling that all C/C++ successor languages including D have turned away from the independent compilation and the use of the preprocessor.

So it's good to know that Ada has had separate compilation ever since Ada-83 and is probably the most sophisticated implementation around.

Parts of a package

A package generally consists of two parts, the specification and the body. A package specification can be further divided in two logical parts, the visible part and the private part. Only the visible part of the specification is mandatory. The private part of the specification is optional, and a package specification might not have a package body—the package body only exists to complete any incomplete items in the specification. Subprogram declarations are the most common incomplete items. There must not be a package body if there is no incomplete declaration, and there has to be a package body if there is some incomplete declaration in the specification.

To understand the value of the three-way division, consider the case of a package that has already been released and is in use. A change to the visible part of the specification will require that the programmers of all using software verify that the change does not affect the using code. A change to the private part of the declaration will require that all using code be recompiled but no review is normally needed. Some changes to the private part can change the meaning of the client code however. An example is changing a private record type into a private access type. This change can be done with changes in the private part, but change the semantic meaning of assignment in the clients code. A change to the package body will only require that the file containing the package body be recompiled, because nothing outside of the package body can ever access anything within the package body (beyond the declarations in the specification part).

A common usage of the three parts is to declare the existence of a type and some subprograms that operate on that type in the visible part, define the actual structure of the type (e.g. as a record) in the private part, and provide the code to implement the subprograms in the package body.

The package specification — the visible part

The visible part of a package specification describes all the subprogram specifications, variables, types, constants etc. that are visible to anyone who wishes to use the package.

package Public_Only_Package is

  type Range_10 is range 1 .. 10;

end Public_Only_Package;

Since Range_10 is an integer type, there are a lot of operations declared implicitly in this package.

The private part

The private part of a package serves two purposes:

  • To complete the deferred definition of private types and constants.
  • To export entities only visible to the children of the package.
package Package_With_Private is
     
   type Private_Type is private;

private

   type Private_Type is array (1 .. 10) of Integer;

end Package_With_Private;

Since the type is private, clients cannot make any use of it as long as there are no operations defined in the visible part.

The package body

The package body defines the implementation of the package. All the subprograms defined in the specification have to be implemented in the body. New subprograms, types and objects can be defined in the body that are not visible to the users of the package.

package Package_With_Body is

   type Basic_Record is private;

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     Integer);

   function Get_A (This : Basic_Record) return Integer;

private

   type Basic_Record is 
      record 
         A : Integer;
      end record ;

   pragma Pure_Function  (Get_A);  -- not a standard Ada pragma
   pragma Inline (Get_A);
   pragma Inline (Set_A);

end Package_With_Body;
package body Package_With_Body is

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     Integer) is
   begin
      This.A := An_A;
   end Set_A;

   function Get_A (This : Basic_Record) return Integer is
   begin
      return This.A;
   end Get_A;

end Package_With_Body;
pragma Pure_Function
Only available when using GNAT.

Two Flavors of Package

The packages above each define a type together with operations of the type. When the type's composition is placed in the private part of a package, the package then exports what is known to be an Abstract Data Type or ADT for short. Objects of the type are then constructed by calling one of the subprograms associated with the respective type.

A different kind of package is the Abstract State Machine or ASM. A package will be modeling a single item of the problem domain, such as the motor of a car. If a program controls one car, there is typically just one motor, or the motor. The public part of the package specification only declares the operations of the module (of the motor, say), but no type. All data of the module are hidden in the body of the package where they act as state variables to be queried, or manipulated by the subprograms of the package. The initialization part sets the state variables to their initial values.

package Package_With_Body is

   procedure Set_A (An_A : in Integer);

   function Get_A return Integer;

private

   pragma Pure_Function (Get_A);—not a standard Ada pragma

end Package_With_Body;
package body Package_With_Body is

   The_A: Integer;

   procedure Set_A (An_A : in Integer) is
   begin
      The_A := An_A;
   end Set_A;

   function Get_A return Integer is
   begin
      return The_A;
   end Get_A;


begin

   The_A := 0;

end Package_With_Body;

(A note on construction: The package initialization part after begin corresponds to a construction subprogram of an ADT package. However, as a state machine is an “object” already, “construction” is happening during package initialization. (Here it sets the state variable The_A to its initial value.) An ASM package can be viewed as a singleton.)

Using packages

To utilize a package it's needed to name it in a with clause, whereas to have direct visibility of that package it's needed to name it in a use clause.

For C++ programmers, Ada's with clause is analogous to the C++ preprocessor's #include and Ada's use is similar to the using namespace statement in C++. In particular, use leads to the same namespace pollution problems as using namespace and thus should be used sparingly. Renaming can shorten long compound names to a manageable length, while the use type clause makes a type's operators visible. These features reduce the need for plain use.

Standard with

The standard with clause provides visibility for the public part of a unit to the following defined unit. The imported package can be used in any part of the defined unit, including the body when the clause is used in the specification.

Private with

This language feature is only available from Ada 2005 on.

private with Ada.Strings.Unbounded; 

package Private_With is

   -- The package Ada.String.Unbounded is not visible at this point

   type Basic_Record is private;

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     String);

   function Get_A (This : Basic_Record) return String;

private
   -- The visibility of package Ada.String.Unbounded starts here

   package Unbounded renames Ada.Strings.Unbounded;

   type Basic_Record is 
      record 
         A : Unbounded.Unbounded_String;
      end record;

   pragma Pure_Function  (Get_A);
   pragma Inline (Get_A);
   pragma Inline (Set_A);

end Private_With;
package body Private_With is

   -- The private withed package is visible in the body too

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     String)
   is
   begin
      This.A := Unbounded.To_Unbounded_String (An_A);
   end Set_A;

   function Get_A (This : Basic_Record) return String is
   begin
      return Unbounded.To_String (This.A);
   end Get_A;

end Private_With;

Limited with

This language feature is only available from Ada 2005 on.

The limited with can be used to represent two mutually dependent type (or more) located in separate packages.

limited with Departments;

package Employees is

   type Employee is tagged private;

   procedure Assign_Employee
     (E : in out Employee;
      D : access Departments.Department'Class);

   type Dept_Ptr is access all Departments.Department'Class;

   function Current_Department(E : in Employee) return Dept_Ptr;
   ...
end Employees;
limited with Employees;

package Departments is

   type Department is tagged private;

   procedure Choose_Manager
     (Dept    : in out Department;
      Manager : access Employees.Employee'Class);
   ...
end Departments;

Making operators visible

Suppose you have a package Universe that defines some numeric type T.

with Universe;
procedure P is
  V: Universe.T := 10.0;
begin
  V := V * 42.0;  --  illegal
end P;

This program fragment is illegal since the operators implicitly defined in Universe are not directly visible.

You have four choices to make the program legal.

Use a use_package_clause. This makes all declarations in Universe directly visible (provided they are not hidden because of other homographs).

with Universe;
use  Universe;
procedure P is
  V: Universe.T := 10.0;
begin
  V := V * 42.0;
end P;

Use renaming. This is error prone since if you rename many operators, cut and paste errors are probable.

with Universe;
procedure P is
  function "*" (Left, Right: Universe.T) return Universe.T renames Universe."*";
  function "/" (Left, Right: Universe.T) return Universe.T renames Universe."*";  --  oops
  V: Universe.T := 10.0;
begin
  V := V * 42.0;
end P;

Use qualification. This is extremely ugly and unreadable.

with Universe;
procedure P is
  V: Universe.T := 10.0;
begin
  V := Universe."*" (V, 42.0);
end P;

Use the use_type_clause. This makes only the operators in Universe directly visible.

with Universe;
procedure P is
  V: Universe.T := 10.0;
  use type Universe.T;
begin
  V := V * 42.0;
end P;

There is a special beauty in the use_type_clause. Suppose you have a set of packages like so:

with Universe;
package Pack is
  subtype T is Universe.T;
end Pack;
with Pack;
procedure P is
  V: Pack.T := 10.0;
begin
  V := V * 42.0;  --  illegal
end P;

Now you've got into trouble. Since Universe is not made visible, you cannot use a use_package_clause for Universe to make the operator directly visible, nor can you use qualification for the same reason. Also a use_package_clause for Pack does not help, since the operator is not defined in Pack. The effect of the above construct means that the operator is not nameable, i.e. it cannot be renamed in a renaming statement.

Of course you can add Universe to the context clause, but this may be impossible due to some other reasons (e.g. coding standards); also adding the operators to Pack may be forbidden or not feasible. So what to do?

The solution is simple. Use the use_type_clause for Pack.T and all is well!

with Pack;
procedure P is
  V: Pack.T := 10.0;
  use type Pack.T;
begin
  V := V * 42.0;