Ada’s type system is one of the most powerful and distinctive features in all of programming. Where most languages use types as loose labels that the programmer must remember to respect, Ada treats types as enforceable contracts. The compiler actively prevents you from mixing incompatible values — even if they have the same underlying representation. This isn’t just academic strictness; it’s the foundation that makes Ada the language of choice for avionics, medical devices, and defense systems.
Ada uses static, strong, and safe typing. Every variable must be declared with a type before use, the compiler rejects implicit conversions between unrelated types, and runtime checks catch values that fall outside their declared ranges. This triple layer of safety means that entire classes of bugs — unit confusion, integer overflow, out-of-range indexing — are caught before your code ever runs in production.
In this tutorial you’ll learn how Ada handles variable declarations, explore the built-in scalar types, work with Ada’s derived types and subtypes, and see how type conversions and constants work in practice.
Variable Declarations and Basic Types
Ada requires every variable to be declared with an explicit type in the declarative region — between is and begin. This is a deliberate design choice: the compiler knows every variable’s type before any code executes, enabling thorough compile-time checking.
Create a file named variables.adb:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| with Ada.Text_IO;
with Ada.Integer_Text_IO;
with Ada.Float_Text_IO;
procedure Variables is
-- Integer types
Count : Integer := 42;
Max_Size : Integer := 1_000_000; -- underscores improve readability
Negative : Integer := -17;
-- Floating point types
Pi : Float := 3.14159;
Temperature : Float := 98.6;
-- Boolean type
Is_Active : Boolean := True;
Is_Done : Boolean := False;
-- Character type
Letter : Character := 'A';
Digit : Character := '7';
-- String type (fixed-length in Ada)
Name : String (1 .. 5) := "Ada ";
Greeting : String := "Hello";
-- Natural and Positive subtypes
Index : Natural := 0; -- Natural: 0 .. Integer'Last
Size : Positive := 1; -- Positive: 1 .. Integer'Last
begin
Ada.Text_IO.Put_Line ("=== Basic Variables ===");
Ada.Text_IO.Put ("Count = ");
Ada.Integer_Text_IO.Put (Count, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Max_Size = ");
Ada.Integer_Text_IO.Put (Max_Size, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Negative = ");
Ada.Integer_Text_IO.Put (Negative, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Floating Point ===");
Ada.Text_IO.Put ("Pi = ");
Ada.Float_Text_IO.Put (Pi, Fore => 1, Aft => 5, Exp => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Temperature = ");
Ada.Float_Text_IO.Put (Temperature, Fore => 2, Aft => 1, Exp => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Boolean ===");
Ada.Text_IO.Put_Line ("Is_Active = " & Boolean'Image (Is_Active));
Ada.Text_IO.Put_Line ("Is_Done = " & Boolean'Image (Is_Done));
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Character ===");
Ada.Text_IO.Put_Line ("Letter = " & Letter);
Ada.Text_IO.Put_Line ("Digit = " & Digit);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== String ===");
Ada.Text_IO.Put_Line ("Name = """ & Name & """");
Ada.Text_IO.Put_Line ("Greeting = """ & Greeting & """");
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Subtypes ===");
Ada.Text_IO.Put ("Index (Natural) = ");
Ada.Integer_Text_IO.Put (Index, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Size (Positive) = ");
Ada.Integer_Text_IO.Put (Size, Width => 0);
Ada.Text_IO.New_Line;
end Variables;
|
Derived Types, Subtypes, and Constants
One of Ada’s most distinctive features is the ability to create new types that are incompatible with each other, even when they share the same underlying representation. This prevents accidental mixing of logically different quantities — the kind of error that contributed to the Mars Climate Orbiter loss.
Create a file named variables_types.adb:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
| with Ada.Text_IO;
with Ada.Integer_Text_IO;
with Ada.Float_Text_IO;
procedure Variables_Types is
-- Derived types: new types incompatible with their parent
type Meters is new Float;
type Feet is new Float;
-- You cannot add Meters and Feet without explicit conversion
Distance_M : Meters := 100.0;
Distance_F : Feet := 328.0;
-- Range types: compiler and runtime enforce bounds
type Percentage is range 0 .. 100;
type Day_Of_Month is range 1 .. 31;
Score : Percentage := 85;
Today : Day_Of_Month := 15;
-- Enumeration types
type Color is (Red, Green, Blue, Yellow);
type Direction is (North, South, East, West);
Favorite : Color := Blue;
Heading : Direction := North;
-- Subtypes: constrained views of existing types
subtype Small_Int is Integer range -100 .. 100;
subtype Uppercase is Character range 'A' .. 'Z';
Offset : Small_Int := 42;
Initial : Uppercase := 'B';
-- Constants
Max_Retries : constant Integer := 3;
Pi_Const : constant Float := 3.14159_26535;
App_Name : constant String := "CodeArchaeology";
-- Modular (unsigned) types
type Byte is mod 256; -- wraps around: 255 + 1 = 0
Data : Byte := 200;
-- Type conversions
Int_Value : Integer := 42;
Float_Value : Float;
-- Packages for derived type I/O
package Meters_IO is new Ada.Text_IO.Float_IO (Meters);
package Feet_IO is new Ada.Text_IO.Float_IO (Feet);
package Percentage_IO is new Ada.Text_IO.Integer_IO (Percentage);
package Day_IO is new Ada.Text_IO.Integer_IO (Day_Of_Month);
package Byte_IO is new Ada.Text_IO.Modular_IO (Byte);
begin
Ada.Text_IO.Put_Line ("=== Derived Types ===");
Ada.Text_IO.Put ("Distance_M = ");
Meters_IO.Put (Distance_M, Fore => 3, Aft => 1, Exp => 0);
Ada.Text_IO.Put_Line (" meters");
Ada.Text_IO.Put ("Distance_F = ");
Feet_IO.Put (Distance_F, Fore => 3, Aft => 1, Exp => 0);
Ada.Text_IO.Put_Line (" feet");
-- Convert Meters to Feet: explicit conversion required
Distance_F := Feet (Float (Distance_M) * 3.28084);
Ada.Text_IO.Put ("100m in feet = ");
Feet_IO.Put (Distance_F, Fore => 3, Aft => 1, Exp => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Range Types ===");
Ada.Text_IO.Put ("Score (0..100) = ");
Percentage_IO.Put (Score, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Today (1..31) = ");
Day_IO.Put (Today, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Enumeration Types ===");
Ada.Text_IO.Put_Line ("Favorite = " & Color'Image (Favorite));
Ada.Text_IO.Put_Line ("Heading = " & Direction'Image (Heading));
Ada.Text_IO.Put_Line ("Next color = " & Color'Image (Color'Succ (Favorite)));
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Subtypes ===");
Ada.Text_IO.Put ("Offset (Small_Int) = ");
Ada.Integer_Text_IO.Put (Integer (Offset), Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("Initial (Uppercase) = " & Initial);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Constants ===");
Ada.Text_IO.Put ("Max_Retries = ");
Ada.Integer_Text_IO.Put (Max_Retries, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Pi_Const = ");
Ada.Float_Text_IO.Put (Pi_Const, Fore => 1, Aft => 11, Exp => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("App_Name = " & App_Name);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Modular Type ===");
Ada.Text_IO.Put ("Data = ");
Byte_IO.Put (Data, Width => 0);
Ada.Text_IO.New_Line;
Data := Data + 100; -- wraps: 200 + 100 = 44 (mod 256)
Ada.Text_IO.Put ("Data + 100 = ");
Byte_IO.Put (Data, Width => 0);
Ada.Text_IO.Put_Line (" (wraps mod 256)");
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Type Conversions ===");
Float_Value := Float (Int_Value);
Ada.Text_IO.Put ("Integer 42 as Float = ");
Ada.Float_Text_IO.Put (Float_Value, Fore => 2, Aft => 1, Exp => 0);
Ada.Text_IO.New_Line;
Int_Value := Integer (3.7); -- rounds to nearest: 4
Ada.Text_IO.Put ("Float 3.7 as Integer = ");
Ada.Integer_Text_IO.Put (Int_Value, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.New_Line;
Ada.Text_IO.Put_Line ("=== Type Attributes ===");
Ada.Text_IO.Put ("Integer'First = ");
Ada.Integer_Text_IO.Put (Integer'First, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Integer'Last = ");
Ada.Integer_Text_IO.Put (Integer'Last, Width => 0);
Ada.Text_IO.New_Line;
Ada.Text_IO.Put ("Integer'Size = ");
Ada.Integer_Text_IO.Put (Integer'Size, Width => 0);
Ada.Text_IO.Put_Line (" bits");
end Variables_Types;
|
Running with Docker
1
2
3
4
5
6
7
8
| # Pull the Ada image
docker pull codearchaeology/ada:latest
# Run the basic variables example
docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest sh -c 'gnatmake variables.adb && ./variables'
# Run the types example
docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest sh -c 'gnatmake variables_types.adb && ./variables_types'
|
Expected Output
Output from variables.adb:
=== Basic Variables ===
Count = 42
Max_Size = 1000000
Negative = -17
=== Floating Point ===
Pi = 3.14159
Temperature = 98.6
=== Boolean ===
Is_Active = TRUE
Is_Done = FALSE
=== Character ===
Letter = A
Digit = 7
=== String ===
Name = "Ada "
Greeting = "Hello"
=== Subtypes ===
Index (Natural) = 0
Size (Positive) = 1
Output from variables_types.adb:
=== Derived Types ===
Distance_M = 100.0 meters
Distance_F = 328.0 feet
100m in feet = 328.1
=== Range Types ===
Score (0..100) = 85
Today (1..31) = 15
=== Enumeration Types ===
Favorite = BLUE
Heading = NORTH
Next color = YELLOW
=== Subtypes ===
Offset (Small_Int) = 42
Initial (Uppercase) = B
=== Constants ===
Max_Retries = 3
Pi_Const = 3.14159265350
App_Name = CodeArchaeology
=== Modular Type ===
Data = 200
Data + 100 = 44 (wraps mod 256)
=== Type Conversions ===
Integer 42 as Float = 42.0
Float 3.7 as Integer = 4
=== Type Attributes ===
Integer'First = -2147483648
Integer'Last = 2147483647
Integer'Size = 32 bits
Key Concepts
- Explicit declarations required — Every variable must be declared with a type before
begin; Ada never infers variable types from assignment - Derived types enforce separation —
type Meters is new Float creates a type that is incompatible with plain Float or other derived types, preventing accidental mixing of unrelated quantities - Range types catch invalid values —
type Percentage is range 0 .. 100 enforces bounds at both compile time and runtime, raising Constraint_Error if a value falls outside the range - Subtypes constrain without creating new types — A
subtype shares the parent type’s identity but adds restrictions; values are assignment-compatible with the parent type - Enumeration types are first-class — Enumerations have attributes like
'Image, 'Succ, 'Pos, and 'Val built into the language, not bolted on as an afterthought - Constants use the
constant keyword — Unlike convention-based approaches in dynamic languages, Ada constants are enforced by the compiler and cannot be reassigned - Modular types provide unsigned arithmetic —
type Byte is mod 256 gives you wrap-around behavior, useful for low-level systems programming - Type attributes are powerful — Every type supports attributes like
'First, 'Last, 'Size, and 'Image that provide metadata about the type at compile time or runtime