I/O Operations in Ada
Master input and output in Ada - formatted console output, reading from stdin, file reading and writing, and robust I/O exception handling with GNAT
Input and output are where a program meets the outside world, and Ada treats that boundary with the same seriousness it applies to everything else. Rather than a single grab-bag library, Ada ships a family of strongly typed I/O packages: Ada.Text_IO for human-readable text, Ada.Integer_Text_IO and Ada.Float_Text_IO for numeric formatting, and the same File_Type abstraction for both the terminal and files on disk.
In the Hello World tutorial you saw Put_Line write a string to the console. This tutorial goes much further: precise column-aligned numeric output, reading typed values from standard input, creating and reading files, and - crucially for a safety-critical language - handling the exceptions that I/O can raise. Because I/O failures (a missing file, a bad number, an unexpected end of file) are real-world certainties, Ada surfaces them as named exceptions you are expected to catch.
As a strongly typed language, Ada will not silently coerce a string into a number or let you read past the end of a file. Every read has a type, every file has a mode, and every error has a name. The result is verbose compared to a dynamic language, but it is exactly this explicitness that keeps Ada code running unattended in aircraft and trains for decades.
Formatted Console Output
Put_Line and Put write strings, but real programs need to format numbers into columns and control decimal places. Ada separates these concerns into per-type packages. Ada.Integer_Text_IO gives integers a Width parameter for right-aligned columns, and Ada.Float_Text_IO gives floats Fore (digits before the point), Aft (digits after), and Exp (exponent width, 0 to disable scientific notation).
Create a file named io_basics.adb:
| |
Two idioms are worth noting. Integer'Image (Count) is the attribute form: it converts any integer to a String and prepends a space for non-negative values (and a - for negatives), which is why "Count is" & Integer'Image (Count) reads cleanly. The Put from Ada.Integer_Text_IO, by contrast, is overloaded for numeric types and pads to a field width instead of concatenating into a string.
Reading Input from the Terminal
Reading input mirrors writing it. Get_Line reads a whole line of text into a fixed-size String and reports how many characters were actually read through its Last out-parameter - you then slice the buffer with Name (1 .. Last). For numbers, the overloaded Get from Ada.Integer_Text_IO skips leading whitespace and parses a typed value, raising Data_Error if the text is not a valid integer.
Create a file named read_input.adb:
| |
Because this program reads from standard input, run it with input piped in (note the -i flag so Docker keeps stdin open):
| |
With the piped input above, the program prints:
Enter your name: Enter your age: Hello, Ada Lovelace!
Next year you will be 37
The two prompts appear back-to-back because piped input is not echoed to the terminal. Passing Width => 0 to the integer Put requests the minimal field width, so 37 appears with no padding.
Writing and Reading Files
Files use the very same Ada.Text_IO package and the same Put_Line/Get_Line subprograms - the only difference is that they take a File_Type value as their first argument. You obtain that value by declaring a File_Type variable and calling Create (to make a new file for writing) or Open (for an existing file). Each file carries a mode: Out_File to write, In_File to read, or Append_File to add to the end. Always Close the file when finished so buffered data is flushed.
Create a file named file_io.adb:
| |
The while not End_Of_File (Input) loop pattern is the canonical way to read a text file to its end in Ada. End_Of_File returns True once there is nothing left to read, so the loop stops cleanly without ever reading past the end. This is the program we will run with Docker below, since it produces deterministic output with no external input.
Handling I/O Exceptions
I/O is where the real world intrudes: files go missing, disks fill up, and data arrives malformed. Ada models each of these as a named exception declared in Ada.Text_IO. The most common are Name_Error (the file named in Open does not exist), End_Error (an attempt to read past end of file), Data_Error (text that does not match the expected type), and Use_Error (an operation the file system refuses). A begin ... exception ... end block lets you respond to each by name and keep running.
Create a file named io_exceptions.adb:
| |
Because missing.txt does not exist, Open raises Name_Error, the matching handler runs, and execution continues past the block - exactly the controlled recovery a long-running system needs. The when others arm is a catch-all safety net for any exception you did not name explicitly. This is Ada’s I/O philosophy in miniature: failures are not crashes to be feared but named events to be handled.
Running with Docker
The file_io.adb example is fully self-contained - it writes a file and reads it back without any terminal input - so it makes the cleanest demonstration:
| |
The -v $(pwd):/app flag mounts your current directory into the container, so the notes.txt file the program creates appears in your working directory after the run. To try the other examples, swap file_io for io_basics or io_exceptions in the command above.
Expected Output
Running the file_io example produces:
Wrote notes.txt successfully.
--- Contents of notes.txt ---
Ada was named after Ada Lovelace.
It was standardized in 1983.
GNAT is the GNU Ada compiler.
After the run, a notes.txt file containing those three lines remains in your working directory.
Key Concepts
- One abstraction for console and files -
Ada.Text_IOuses the samePut_LineandGet_Linesubprograms for both; passing aFile_Typefirst argument is the only difference. - Typed numeric I/O lives in its own packages -
Ada.Integer_Text_IOandAda.Float_Text_IOadd formatting parameters likeWidth,Fore,Aft, andExpfor column-aligned, fixed-precision output. Get_Linereports its length - reading into a fixedStringreturns aLastout-parameter; always slice with(1 .. Last)rather than using the whole buffer.- Files have explicit modes -
CreatewithOut_Fileto write,OpenwithIn_Fileto read,Append_Fileto extend; alwaysCloseto flush buffered data. End_Of_Filedrives read loops -while not End_Of_File (F) loopis the idiomatic way to consume a text file to its end without reading past it.- I/O errors are named exceptions -
Name_Error,End_Error,Data_Error, andUse_Errorlet you recover precisely, andwhen othersprovides a catch-all. 'Imageversus formattedPut-Integer'Imagereturns aString(with a leading space) for concatenation, while the overloadedPutpads numbers into a field width for tabular output.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/ada:latest
Comments
Loading comments...
Leave a Comment