Variables and Constants

Variables and constants both store data. The difference between them is that the data stored in a variable can be changed during program execution, whereas the data stored in a constant cannot. Both must be declared as a specific type, but only the constant requires a value (the data) to be assigned (initialized) when it is declared.

In Ada, variables and constants are actually called objects. This has nothing to do with object oriented programming. A variable in Ada is an object that has been declared as a given type and with an optional initial value. A constant is the same, except the declaration now contains the reserved word constant and the initial value is no longer optional, but required.

Both variables and constants are declared in the declarative part of a block. More can be read about the structure of an Ada program here.

Some general advice on how to name variables and constants is given here.

Variables

Variables are a core part of any program - without them, we would not be able to create programs that go much beyond the classic “Hello World!” example. Variables in Ada must be declared. They cannot just magically appear in a program without first having been declared. A variable declaration looks like this:

Variable_Name : Variable_Type [:=Optional Value]

If a variable is assigned a value when declared, it is said to be initialized. It is possible to declare more than one variable of the same type in one go by comma-separating the variable names:

Variable_A, Variable_B : Variable_Type [:=Optional Value]

As you can see, the assignment of a value to a variable is optional. All the compiler needs to know, is what kind of data (the type) you intend the variable to hold. It doesn't care if there's actual data in the variable or not. When you declare a variable, you're basically just asking for a specific amount of memory to be set aside. The actual amount of memory depends, of course, on the declared type. An Integer might take up 4 bytes of memory, and a String (1 .. 10) might take up 10 bytes of memory. The variable name is nothing more than a symbolic name, associated with whatever value that is placed in the memory reserved to the variable.

If we were to assign a value to a variable during its declaration, it would look like this:

Variable_Name : Variable_Type := Variable_Value

Let's try it with some real Ada variables. As seen above, a variable in Ada must be declared as pointing to a specific type of data:

A_String_Variable : String (1 .. 10);

Here we declare the variable A_String_Variable as a String type, the length of which is 10 characters, (1 .. 10). Lets see how it looks if we assign a value to A_String_Variable during its declaration:

A_String_Variable : String (1 .. 10) := "abcdefghij";

Here we've first declared and then initialized the A_String_Variable variable.

In the case of the String type, the length can be omitted and we can instead simply write:

A_String_Variable : String := "abcdefghij";

Here the assignment of “abcdefghij” implicitly declares A_String_Variable to be of length (1 .. 10), because that is the length of the assigned string.

Variables can be declared as any type, be it String, Integer, records, array or homegrown types. Lets try and declare a variety of variables:

An_Array : array (1 .. 3) of Integer := (1, 2, 3);
A_Positive : Positive := 10;
 
type Colors is (Red, Blue, Green);
Color : Colors := Red;
type Person is record
   Name : String (1 .. 12);
   Age : Natural;
end record;
A_Person : Person;
B_Person : Person := (Name => "Thomas Løcke", Age => 37);

Notice the homegrown Colors type. First we declare the new type, and then we declare the Color variable the of that type. This is one of the biggest strengths of Ada: The ability to declare your own types and subtypes. More can be found on this subject in the types and subtypes article.

Now, let's try and use the above declared variables in an actual program:

with Ada.Text_IO;
 
procedure VarCon is
   package IO renames Ada.Text_IO;
 
   A_String : String (1 .. 8) := "A_String";
   B_String : String := "B_String";
 
   An_Array : array (1 .. 3) of Integer := (1, 2, 3);
 
   A_Integer : Integer := 10;
 
   type Colors is (Red, Blue, Green);
   package IOENUM is new Ada.Text_IO.Enumeration_IO (Colors);
   Color : Colors := Red;
 
   type Person is record
      Name : String (1 .. 12);
      Age : Natural;
   end record;
   A_Person : Person;
begin
   IO.Put_Line (Item => A_String);
   IO.Put_Line (Item => B_String);
 
   A_String := "String_A";
   B_String := "String_B";
 
   IO.Put_Line (Item => A_String);
   IO.Put_Line (Item => B_String);
 
   for i in An_Array'Range loop
      IO.Put (Item => i'Img & ":" & An_Array (i)'Img);
      IO.New_Line;
   end loop;
 
   An_Array := (1 => 10, 2 => 20, 3 => 30);
 
   for i in An_Array'Range loop
      IO.Put (Item => i'Img & ":" & An_Array (i)'Img);
      IO.New_Line;
   end loop;
 
   IO.Put_Line (Item => A_Integer'Img);
 
   IOENUM.Put (Item => Color);
   IO.New_Line;
 
   A_Person := (Name => "Thomas Løcke",
                Age => 37);
   IO.Put_Line (Item => A_Person.Name);
end VarCon;

When executed, the above program should output the following:

A_String B_String String_A String_B

1: 1
2: 2
3: 3
1: 10
2: 20
3: 30
10
RED
Thomas Løcke

Here we can actually see why variables are called variables: Their values vary.

Notice how we are able to change the data contained in, for example, the A_String variable. When we declare it, we assign the value A_String (the variable is said to be initialized), but a little later we assign another value to A_String: String_A. That right there is the core concept of variables that every beginning programmer must understand: Variables vary. A variable name is just a reference to a place in memory where a a specified type of data resides. The exact data can, and probably will, change a lot during the run of a program, but the variable name and the type remains the same.

If a variable doesn't change during program execution, then we have a better option: A constant. These are the stiff upper lip siblings of variables.

Constants

Constants are an important tool to help make your programs more reliable and maintainable. A constant is, just like a variable, a reference to a specific type of data, but where they differ from variables is in their constant nature. Variables vary, constants are constant. When declared and initialized, the data they reference is static and can no longer be altered. If you try to alter a constant, the compiler will complain, loudly.

Let's see how we declare and initialize a constant:

Name : constant String (1 .. 12) := "Thomas Løcke";

Or

Name : constant String := "Thomas Løcke";

The only differences from a variable are the reserved constant keyword and the fact that a constant must be initialized when declared. Other than that, declaring and initializing constants is done exactly the same way as variables and it works for all types:

A_Array : constant array (1 .. 3) of Integer := (1, 2, 3);
An_Positive : constant Positive := 10;
 
type Colors is (Red, Blue, Green);
Color : constant Colors := Red;
type Person is record
   Name : String (1 .. 12);
   Age : Natural;
end record;
B_Person : constant Person := (Name => "Thomas Løcke", Age => 37);

If you try to alter a constant in your program, the compiler will complain with a message looking something like this:

constants.adb:15:04: left hand side of assignment must be a variable
gnatmake: "/path/to/constants.adb" compilation error

You should use constants whenever data is supposed to be static. Do not underestimate the human capacity for making mistakes by declaring for example Pi like this:

Pi : Float := 3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511;

Is Pi supposed to change during program execution? Probably not. So why allow for that? Why risk it happening by mistake? Instead do this:

Pi : constant := 3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511;

There. Now you can feel safe in knowing that Pi will be Pi, no matter what.

By the way, for Pi specifically (and most notable constants), you needn't declare it yourself: It's already done for you in the Ada.Numerics package.

Named numbers

The attentive reader will have noticed that the Pi constant used above is declared without a type. Such constants are called named numbers and their type is called an universal type. There are four kinds of universal types:

  • universal_integer
  • universal_real
  • universal_fixed
  • universal_access

You cannot explicitly declare an object to be of type universal_integer. But what you can do is declare an object to be a constant named number. These may take values of any size or precision, as opposed to the types derived from these, for example Integer and Float. The universal types are not bound by the same constraints as the types derived from them:

Large_Int : Integer := 4_294_967_296;        --  Value not in range on a 32 bit machine
Named_Large_Int : constant := 4_294_967_296; --  OK on a 32 bit machine

The Named_Large_Int constant is of type universal_integer, because the literal 4_294_967_296 is an integer. Had we instead written:

Named_Real : constant := 4.294_967_296;

then Named_Real is of the type universal_real because the literal 4.294_967_296 contains a point.

Scope

Finally let's take a short look at variable scope in regards to blocks. When and where are they visible? This is perhaps best understood by a simple program:

with Ada.Text_IO;
 
procedure Scope is
   package IO renames Ada.Text_IO;
 
   Name : String := "Thomas Løcke";
begin
   IO.Put_Line (Item => "1:" & Name);
 
   declare
      Name : String := "Dwight S. Miller"; 
   begin
      IO.Put_Line (Item => "2:" & Name); 
   end;
 
   IO.Put_Line (Item => "3:" & Name);
 
   declare
   begin
      IO.Put_Line (Item => "4:" & Name); 
   end;
end Scope;

The output we get from the above looks like this:

1:Thomas Løcke
2:Dwight S. Miller
3:Thomas Løcke
4:Thomas Løcke

Note how Name is declared as a local variable in the first block. The declaration in the first block does not in any way interfere with the “global” Name variable. If no Name variable is declared for a block, the “global” Name variable is used, as can be seen in the final block, where Thomas Løcke is printed, even though no such assignment has been done in the block.

So variable scope in regards to blocks is very straightforward: Blocks inherit variables from their parent(s) and these can then be overridden locally in the block by declaring the variable anew.


Navigation