learn.adacore.com: New Advanced Ada contents
by Gustavo A. Hoffmann –
A while ago, we announced some updates to the learn website. In the meantime, we published new chapters and sections to the Advanced Journey With Ada course, which we discuss today.
New additions #
We added two chapters to the Resource Management part of the Advanced Journey With Ada course:
a chapter on limited types, and
a chapter on controlled types.
Moreover, we introduced a new chapter on record types by moving sections from other chapters to this new chapter and adding new sections as well.
With these additions, the course’s size increased substantially: the PDF version of the course increased from 651 to 807 pages.
Record Types #
The new chapter on record types includes the previously existing sections on mutually dependent types and null records. In addition, a detailed discussion about default initialization of record types, record discriminants and per-object expressions was added to the chapter.
Limited Types #
Limited types are types that have the following restrictions:
copying objects of limited types via direct assignments is forbidden; and
there’s no predefined equality operator (or inequality operator) for limited types.
We can think of limited types as an easy way to avoid inappropriate semantics. For example, a lock should not be copied — neither directly, via assignment, nor with pass-by-copy. Similarly, a file, which is really a file descriptor, should not be copied.
In this new chapter of the course, we see examples of unwanted side-effects that arise if we don’t use limited types for these cases. In addition, the chapter includes more advanced topics such as explicitly limited types, immutably limited types, limited types with discriminants, and constructor functions for limited types.
Also, when compared to nonlimited types, there are differences when it comes to deriving from limited types and using limited types as parameters, for example. The differences in those two areas (and in other areas as well) are explained in details in the chapter.
Controlled Types #
Controlled types allow us to specify the initialization and finalization semantics for objects of that type. For any controlled object A
, an Initialize (A)
procedure is called right after the object is created, and a Finalize (A)
procedure is called right before the object is actually finalized.
However, controlled objects aren’t the only way to control the initialization of an object. For example, the language specifies that objects of access types are initialized by default to null
. Likewise, we can declare types with a default initial value. Similarly, record types have a very good default initialization capability for the record components. They’re the most common completion for private types, so the facility is often used.
Even though these default initialization methods provide some control over the objects, they might not be enough in certain situations. Also, we don’t have any means to perform useful operations right before an object goes out of scope. In this case, we can use controlled objects to address these limitations.
The choice between using default initialization or controlled types requires some analysis of the specific use-case. Keep in mind that, if only automatic initialization of an object upon creation is needed, then default initialization is the first choice, as it’s guaranteed and requires nothing of the client. In addition, it’s cheap at run-time compared to controlled types. On the other hand, if operations have to be performed at the finalization, then controlled types are the best solution.
The new chapter starts with an overview of controlled types, and then explains each stage of the lifetime of a controlled object (initialization and finalization), and some peculiarities regarding assignments and exception handling.
Finally, applications of controlled types are presented in the chapter. For example, we can use controlled types to encapsulate file handling, so that files are automatically created and closed. A common use-case is when a new file is expected to be created or opened when we declare the controlled object, and closed when the controlled object gets out of scope.
A simple example is the one of a logger, which we can use to write to a logfile by simple calls to Put_Line
:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Finalization;
package Loggers is
type Logger (<>) is limited private;
function Init (Filename : String) return Logger;
procedure Put_Line (L : Logger; S : String);
private
type Logger is new Ada.Finalization.Limited_Controlled with
record
Logfile : File_Type;
end record;
procedure Finalize (L : in out Logger);
end Loggers;
Here, Logger
is a limited controlled type that encapsulates a file (stored in the Logfile
component). The initialization of an object of Logger
type is enforced by the unknown discriminants, which require a call to Init
when the object is declared: this function opens the file descriptor and assigns it to the Logfile
component. The finalization is handled by the Finalize
procedure, which is called when the object goes out of scope: this procedure closes the file. Thus, from the user’s perspective, the logfile is closed automatically. (You can find more details about this specific example — including the complete source code — in the chapter.)
Worth mentioning #
The last addition worth mentioning happened in the chapter on access types, which was extended with a new section on mutually dependent types using access types.
The changelog page has an overview of the chapters and sections that have been added to the course for each release of the learn website. More additions are planned for the upcoming months, so keep tuned!