Стандартная библиотека и первая полноценная программа
Обзорная экскурсия по стандартной библиотеке Ada и сборка первой полноценной программы: что лежит в иерархии Ada, как устроены пакеты Numerics, Calendar, Containers, и как всё это складывается в работающее приложение.
Стандартная библиотека Ada — иерархия пакетов с корнем
Ada(плюсInterfacesиSystem), описанная прямо в стандарте языка и доступная в любой реализации.
Карта стандартной библиотеки
Когда вы освоили ввод-вывод, переменные и пакеты, полезно увидеть карту того, что даёт стандарт «из коробки». В отличие от языков с гигантскими внешними экосистемами, стандартная библиотека Ada сравнительно компактна, но тщательно специфицирована: её поведение — часть стандарта, а значит, одинаково на любой платформе и в любом сертифицированном компиляторе. Это важно для критичных систем: вы не зависите от капризов случайной библиотеки с непредсказуемым качеством. Иерархия делится на три корневых пакета:
Ada— основная библиотека: ввод-вывод, строки, математика, контейнеры, время, исключения.Interfaces— типы фиксированной разрядности и средства взаимодействия с другими языками (C, Fortran).System— низкоуровневые, зависящие от платформы сущности (адреса, приоритеты).
Сосредоточимся на самых полезных дочерних пакетах Ada.
Математика: Ada.Numerics
Пакет Ada.Numerics и его потомки дают математику. Сама вершина содержит фундаментальные константы:
with Ada.Numerics; use Ada.Numerics;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
procedure Show_Pi is
begin
Put ("Число Пи равно ");
Put (Pi, Fore => 1, Aft => 5, Exp => 0); -- Pi из Ada.Numerics
New_Line;
end Show_Pi;
Вывод:
Число Пи равно 3.14159
Элементарные функции (синус, косинус, логарифм, корень) лежат в дочернем пакете Ada.Numerics.Elementary_Functions для типа Float. Подключив его, можно писать Sqrt (2.0), Sin (X), Log (X). Здесь снова проявляется типизированность: функции определены для конкретного вещественного типа, а для других точностей есть обобщённая версия, настраиваемая под нужный тип.
Время: Ada.Calendar
Пакет Ada.Calendar работает с датами и временем. Он даёт тип Time и функцию Clock, возвращающую текущий момент, а также средства разложить время на год, месяц, день, секунды:
with Ada.Calendar; use Ada.Calendar;
-- ...
Now : Time := Clock;
Year : Year_Number := Ada.Calendar.Year (Now);
Тип Year_Number — это, как многое в Ada, поддиапазон целых с осмысленными границами (примерно от 1901 до далёкого будущего). Время — отличный пример того, как Ada оборачивает «сырые» секунды в типобезопасные сущности с проверяемыми диапазонами.
Контейнеры: Ada.Containers
Начиная с Ada 2005, стандарт включает богатую иерархию Ada.Containers — типобезопасные коллекции: векторы (Vectors), двусвязные списки (Doubly_Linked_Lists), отображения по ключу (Hashed_Maps, Ordered_Maps), множества. Это аналоги коллекций из других языков, но с двумя отличиями в духе Ada: они строго типизированы (вектор целых не примет строку) и существуют в обычной и «ограниченной» версиях — последние не используют динамическое выделение памяти, что критично для встраиваемых систем реального времени, где куча запрещена. Подробная работа с контейнерами — продвинутая тема, но важно знать: для типовых структур данных не нужны внешние библиотеки, всё есть в стандарте.
Строки: семейство Ada.Strings
Под Ada.Strings собраны средства работы с текстом: Ada.Strings.Fixed (операции над строками фиксированной длины), Ada.Strings.Bounded (строки с ограниченным максимумом) и Ada.Strings.Unbounded (строки произвольной длины, растущие по мере надобности). Это отражает инженерную реальность: иногда нужна предсказуемая фиксированная память, иногда — гибкость. Ada даёт выбор вместо «одной строки на все случаи». Детально разберём в разделе про составные типы.
Собираем полноценную программу
Теперь объединим всё изученное в разделе — объявления, константы, ввод-вывод, математику — в одну осмысленную программу. Она спросит радиус и посчитает длину окружности и площадь круга:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Numerics; use Ada.Numerics;
procedure Circle is
Radius : Float;
Circumference : Float;
Area : Float;
begin
Put ("Введите радиус: ");
Get (Radius); -- чтение вещественного
Circumference := 2.0 * Pi * Radius;
Area := Pi * Radius * Radius;
New_Line;
Put ("Длина окружности: ");
Put (Circumference, Fore => 1, Aft => 3, Exp => 0);
New_Line;
Put ("Площадь круга: ");
Put (Area, Fore => 1, Aft => 3, Exp => 0);
New_Line;
end Circle;
Если ввести радиус 2.0, программа напечатает:
Длина окружности: 12.566
Площадь круга: 12.566
Разберём ключевые моменты. Все величины — Float, потому что радиус и результаты вещественные. Важная деталь: мы пишем 2.0, а не 2. В Ada вещественный литерал обязан иметь точку: 2.0 — это Float, а 2 — это целое, и смешивать их в одном выражении нельзя без явного преобразования (об этом — целый урок в следующем разделе). Pi пришёл из Ada.Numerics и тоже вещественный, поэтому выражение однородно. Чтение Get (Radius) использует Get из Ada.Float_Text_IO. Форматирование вывода задано именованными аргументами Fore/Aft/Exp.
Как работает под капотом подключение библиотеки
Каждая with-оговорка наверху — это явная декларация зависимости. Компилятор и система сборки по ним строят граф: программа Circle зависит от трёх пакетов, каждый — от своих внутренностей. Принципиально, что эти зависимости видны прямо в исходнике. Открыв любой файл Ada, вы по списку with сразу понимаете, на что он опирается — это часть культуры читаемости. В больших проектах это позволяет инструментам анализировать связи, находить лишние зависимости, понимать, что пересобрать при изменении. Сравните с языками, где зависимости размазаны по коду или прячутся в неявных импортах: там понять структуру системы куда труднее. Стандартная библиотека Ada при этом — надёжная опора: её интерфейсы зафиксированы стандартом и не «уплывут» от версии к версии.
Исключения: что происходит, когда что-то идёт не так
Наша программа про окружность таит ловушку, о которой стоит поговорить, потому что она вводит важнейший механизм языка. Что если пользователь вместо числа введёт буквы? Функция Get не сможет разобрать ввод и поднимет исключение — в данном случае Data_Error. Исключение — это структурированный сигнал об исключительной ситуации, который прерывает обычный ход выполнения и ищет обработчик. Если обработчика нет, программа аварийно завершится с сообщением. Ada позволяет перехватить исключение блоком exception:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
procedure Safe_Read is
X : Float;
begin
Put ("Введите число: ");
Get (X);
Put_Line ("Спасибо!");
exception
when Data_Error =>
Put_Line ("Это не похоже на число.");
end Safe_Read;
Раздел exception в конце тела (перед end) перехватывает указанные исключения. Ветвь when Data_Error => сработает, если в основном блоке возникнет ошибка разбора, и вместо краха программа вежливо сообщит о проблеме. Синтаксис намеренно перекликается с case: те же when и стрелка =>. Можно перехватывать несколько видов исключений разными ветвями и использовать when others => для любого непредусмотренного.
Почему это так важно для философии Ada? Потому что исключения — это часть той же стратегии «дорого ошибиться»: ошибки не игнорируются молча. В языках, где деление на ноль или неверный ввод дают неопределённое поведение или тихий мусор, ошибка просачивается дальше и проявляется катастрофой далеко от своей причины. В Ada нарушение поднимает явное, именованное исключение точно в месте, где оно произошло, — Constraint_Error при выходе за диапазон, Data_Error при неверном вводе, Storage_Error при нехватке памяти. Вы либо обрабатываете его осознанно, либо программа честно останавливается, но в любом случае ошибка видима и локализована, а не размазана. Эта встроенная, типизированная обработка исключительных ситуаций — одно из исходных требований Steelman, и она пронизывает весь язык: почти каждая проверка, о которой мы говорили (границы, типы, полнота), при нарушении выражается через соответствующее исключение. Мы ещё вернёмся к исключениям подробно, но уже сейчас важно понимать: за каждой проверкой Ada стоит механизм, который превращает «что-то пошло не так» в управляемое, наблюдаемое событие.
Полезно осознать и масштаб того, что мы только что затронули: исключения — это не «аварийный костыль», а полноценная, проектируемая часть архитектуры надёжной программы. В критичных системах продумывают стратегию обработки: какие исключения перехватывать локально и восстанавливаться, какие — пропускать наверх к обработчику уровня подсистемы, а какие должны переводить систему в безопасное состояние (graceful degradation). Ada даёт для этого богатый набор: предопределённые исключения (Constraint_Error, Data_Error, Storage_Error, Program_Error), возможность объявлять свои исключения для доменных ошибок, и средства получить информацию о возникшей ситуации через пакет Ada.Exceptions. Так обработка ошибок становится не разрозненными проверками, а связной, документированной частью дизайна — что для систем, которым доверяют жизни, столь же важно, как и основная логика. Мы вернёмся к этому подробно, но уже видно: «что делать, когда что-то пошло не так» в Ada — первоклассный вопрос, а не запоздалая мысль.
Частые ошибки и заблуждения
- Писать целые литералы там, где нужны вещественные.
2и2.0— разные типы; в вещественных выражениях используйте2.0,0.5и т.д. - Искать математику в
Ada.Numericsнапрямую. Константы (Pi) — в корне, а функции (Sqrt,Sin) — в дочернемAda.Numerics.Elementary_Functions, его надо подключить отдельно. - Думать, что для коллекций нужны внешние пакеты. Векторы, списки, отображения есть в
Ada.Containersпрямо в стандарте, причём типобезопасные. - Игнорировать «ограниченные» версии. Для встраиваемых систем важны контейнеры и строки без динамической памяти — стандарт их специально предусматривает.
- Не перечислять зависимости явно. Каждый используемый пакет нужно подключить через
with; неявных импортов в Ada нет, и это сознательно.
Итоги
- Стандартная библиотека Ada компактна, но строго специфицирована стандартом, и потому одинакова на всех платформах — ценное свойство для критичных систем.
- Иерархия делится на корни
Ada(основное),Interfaces(разрядность, межъязыковость) иSystem(низкий уровень). - Ключевые пакеты:
Ada.Numerics(математика),Ada.Calendar(время),Ada.Containers(типобезопасные коллекции), семействоAda.Strings(текст). - В полноценной программе важно соблюдать типы литералов: вещественные пишутся с точкой (
2.0), целые — без. - Все зависимости объявляются явно через
withнаверху файла, что делает структуру программы читаемой и анализируемой.