Те, кто приходят с других языков и по привычке смотрят, что там с указателями, смотрят не туда. Предполагается, что в ультраидиоматичных адских программах голые указатели вообще не проскакивают. А ресурсами управляют, например, контейнеры. Лежит в карте где-то запись, навёл на неё Cursor, написал declare-begin-end, между declare и begin прорубил через renames окно (Ada 2012 reference) в эту запись, и работаешь с ней. При включенных защитах от tampering это окно ещё по принципу RAII удерживает блокировку на контейнере. Как из end вышел, всё, нет окна, нет блокировки, кроме, разве что, от Cursor.
Из тех альтернатив, которые я видел, кажется, что именно в языке Ада сделан удобный выбор. Либо anything can throw. Либо SPARK верифицировал критичный к производительности код, и это даёт мандат на отключение runtime проверок в этой части кода. Как в libsparkcrypto. Или вот ещё, 13й слайд.
Какого-то компромисса здесь не просматривается. Архитекторы Rust, мне кажется, промахнулись, пытаясь нащупать таковой. Чтоб самый обычный плюс и минус, которые везде, были спусковыми крючками апокалипсиса, ну такое. Мне кажется, потенциальным программистам на Rust надо лучше объяснять, в какую петлю они суются.
А, понятно. Исписываем всё в checked_add, checked_sub и checked_mul и думаем, чёрт возьми, вот она, эргономика. Правда, при этом чужие, не столь эргономично написанные библиотеки предательски норовят выдать панику.
В связи с Better-C можно ещё упомянуть CCured и Deputy. CCured — это такой транслятор Си->Си, который делает указатели жирными, но по возможности как можно менее жирными. Специальный анализатор отслеживает все потоки данных и выбирает степень жирности каждого указателя, которой было бы достаточно для того, чтобы все требуемые операции над ним были безопасно реализуемы. Самый плохой указатель WILD, и он как чума всё заражает. Внутри структур, на которые ссылается WILD, могут быть только WILD указатели.
Работа по отмыванию устаревшего кода от грязи начинается с того, что переписыванием фрагментов разработчик пытается устранить WILD совсем. Авторам удалось отмыть от грязи достаточно большие массивы устаревшего кода, и пока они это делали, они обогатили свой транслятор различной полезной семантикой. Чтоб, например, можно было в своём коде делать проверки, а в код, который ну никак нельзя поменять, передавать структуры без жирных указателей, есть расщепление структур.
Из всех проектов по отмыванию унаследованного кода от грязи это самый продвинутый. На пути отмывания от грязи следующим этапом после принуждения к инкапсуляции памяти, казалось бы, должен быть порт на Аду, но нет, Ada is not invented here. В качестве самодостаточного языка программирования, не для старого кода, а для нового, у них самоделка Deputy по тем же лекалам, что и CCured. Для полноты можно упомянуть.
С C++ подобную штуку, похоже, невозможно нормально скрестить, так как и шаблоны, и анализатор минимальной жирности входят в конфликт. Единственно работающие способы отмыть C++ от грязи я вижу в CHERI и Эльбрус2000, то есть, максимально жирные указатели для всего. В то время, как в CCured большую часть указателей автоматом удаётся облегчить, а дальше разработчик устраняет оставшиеся WILD, в C++ эта работа даже не может начаться. Писать на C++ это такое попадалово, что потом вовек не отмыться. К сожалению, никто (Cyclone и др. отмывальщики C) не пошёл другим путём, в сторону Objective-C. Там, похоже, таких проблем, как с C++, нет, и можно было бы наполнить экосистему безопасным кроссплатфоренным GUI и программами на нём.
Для надёжных языков программирования поднятый вопрос актуален, потому что каждый bzip2 с нуля быстро не перепишешь, а если подключишь неотмытый libbzip2, то через него-то тебя и ломанут. Надо исследовать возможности, как отмыть унаследованные библиотеки на Си перед тем, как подключать их в свой код на надёжном языке.
Тут ещё надо уточнить, что такое паники. Вот взять Delphi, Ada, Rust. Казалось бы, везде проверка целочисленных переполнений и диапазонов есть.
Но если вдаться в детали, в Delphi и Ada оно приводит к легко уловимому exception, а в Rust случается паника. А паника в Rust не то же что исключение. Для отлова паники можно использовать функцию, принимающую два замыкания, первое — что нужно сделать, второе — что сделать, если поймали панику. Кажется, это похоже на обработчик исключений, но нет, опять не угадали. Перед тем, как упасть вниз по стеку, до точки, где установлен перехват паник, ещё успевает нагадить в консоль обработчик паник. Насколько я понимаю, этот обработчик тоже можно отключить или заменить более молчаливой версией. В программе такое можно сделать, а в библиотеке? Нормально ли будет, если какая-то библиотека заменит глобальный обработчик паник? Или если будет полагаться на то, что он отключён, и преспокойно себе бросать и ловить паники.
Всё это похоже на Turbo Pascal образца конца 80х. Там тоже целочисленные переполнения и выход за границы диапазона были. НО ВАЖНО КАК ОНИ БЫЛИ. Там же не было структурной обработки исключений. Там было прерывание, прерывание можно было гипотетически перехватить. Структурной обработки исключений нет, поэтому что делать обработчику прерываний, интересный вопрос. Лучшее, что можно было придумать, это на SetJmp/LongJmp сделать что-то вроде SEH с RAII. Как и в случае Rust, не приходится ожидать, что это сделано в библиотеках.
Так что по факту этот механизм не работает, или работает отнюдь не в полную силу, не так, как в других языках программирования.
Языки есть с одним владельцем. Он там поливает территорию вокруг себя, делает благообразно. Но чужие там не ходят. А есть демократичные. Там больше одного производителя, но и анархия. Везде свои сильные и слабые стороны.
Вообще говоря, у Delphi был неплохой задел. Но Delphi — это два языка в одном. Есть один язык, в котором и есть те самые неплохие заделы, и есть язык, который поддерживается.
В первом языке проверка целочисленных переполнений и диапазонов. В первом языке открытые массивы (в терминах ISO Pascal согласованные массивы, conformant arrays). В первом языке отличные от чисел перечисления. В первом языке ARC существует пусть хотя бы на COM интерфейсах.
Но первый язык не развивают, а развивают второй язык, где объекты с ручным управлением памятью, свойства, события и прочая излишне распиаренная хелсберговщина.
Второй язык развивается и продвигается, а первый язык прозябает в стагнации и безвестности. Вот, например, как были со времён Турбо Паскаля проверки целочисленного переполнения и диапазонов, так и остались почти без изменений. Ну разве что Assert добавились. А что, больше проверять нечего стало? nil можно было бы не ломиться разыменовывать, чтоб упереться в EAccessViolation, а проверить. А ещё, как в Ada 2005, можно дать синтаксис, чтоб запретить nil. В Ada 2005 not null может стоять везде, хоть в record. Это возможно сделать, потому что в языке Ada есть три фичи, которые делают возможным и удобным обращаться с такими структурами данных.
Во-первых, при объявлении record можно сразу же и задать значение по умолчанию. Скорее всего, так не будет сделано, но возможность есть.
Во-вторых, есть агрегаты, которые можно использовать везде, не только при инициализации глобальных переменных. И если значение по умолчанию не задано, а null невозможно принять по умолчанию, потому что он запрещён not null, разработчик может использовать агрегат.
В-третьих, можно инициализировать любую переменную, не только лишь глобальную. В языке Ада это с первых версий, с 1983го года, в Delphi это появилось только в этом 2019м году, в версии 10.3 (Rio).
За счёт этого компилятору Ады есть, к чему принудить разработчика. Если же в Delphi всего этого не было, то пропустив азы, сразу в мир надёжности не вскочишь, но хотя бы в аргументах функций можно было бы nil запрещать. Могли. Но не сделали.
Идём далее. В ISO Pascal есть такая возможность, как conformant array parameters. В языке Ada она специального имени не имеет, но unconstrained arrays, если их принимать формальным аргументом, так себя ведут. Существует изначально, с 1983го года. В языке Delphi это называется open arrays. Существует с 90х, версия Delphi 4 или около того. В C++ что-то похожее появилось только в C++20, std::span. Можно такие параметры из обычных массивов делать, можно из динамических. Можно передать по цепочке принятый формальный аргумент такого типа. А ещё можно сузить, можно передать поддиапазон значения. Дебилизм в Delphi состоит в том, что псевдофункция Slice, единственный способ скрафтить суженное значение, может обрезать справа, но не слева, в отличие от Copy, создающей новое значение. Какого-то обоснования такому положению вещей я не вижу. Манипулируя с указателями, я могу добиться того, чтобы в функцию-таки передать обрезанный с другой стороны подмассив, но тогда компилятор не вполне способен проверить, не было ли выхода за границы диапазона. Это выглядит, будто тёмный угол, в который давно никто не заглядывал.
Ещё по мелочи хотелось бы, чтоб Boolean(5) не работал, а бросал исключение.
У меня есть опыт, как я устраивался на работу, включал в проекте проверки ошибок, и каааак повсплывало. Даже то немногое, что есть в Delphi, показало себя очень полезным.
С Адой им не тягаться, но у производителя по разным фичам была фора лет в пятнадцать прежде, чем они дойдут даже до жирафов C++. Всё это время могли бы выезжать на этой волне. И даже когда доходят, могли бы на конференциях выступить, типа
А вот вы знаете, когда вашего Rust ещё даже в проекте не было, у тех, кто программировал на Delphi, уже были и проверки диапазонов, и отлов целочисленных переполнений, и даже с барского стола перепали open arrays. Мы, правда, будучи кусками дебилов, забыли включить их по умолчанию, и чтоб уж дебилизм был последовательным, прорекламировать включить тоже забыли, но теперь-то мы исправимся. И по умолчанию включим, и Slice починим, и всё это последовательно будет, а не спонтанными подачками.
Сложный, противоречивый язык программирования получается. Отличные заделы плюс безбожный слив со стороны производителя.
Рассмотрены фундаментальные концепции и принципы, воплощенные в современных и перспективных языках программирования. Представлены разные стили программирования (операционный, ситуационный, функциональный, реляционный, параллельный, объектно ориентированный). Базовые концепции и принципы рассмотрены с пяти различных позиций (технологической, авторской, математической, семиотической и реализаторской) и проиллюстрированы примерами из таких языков, как Паскаль, Симула-67, Смолток, Рефал, Ада, Модула-2, Оберон, Оккам-2, Турбо Паскаль, С++ и др. Сложность выделена как основополагающая проблема программирования, а абстракция-конкретизация и прогнозирование-контроль – как основные ортогональные методы борьбы со сложностью. На этой общей базе в книге впервые представлена цельная система концепций и принципов, создающая чёткие ориентиры в области языков программирования. На основе этой системы сформулированы оригинальные положения, указывающие перспективы развития в этой области (модули исключительных ситуаций, модули управления представлением, входовые типы и др.). Многие из них в последние годы стали реальностью. Новые подходы применены при изложении известных фактов (пошаговая модификация нормальных алгоритмов Маркова сначала до Рефала, а затем до реляционных языков, сопоставление принципов «сундука» и «чемоданчика» при создании Ады, Модулы-2 и Оберона, развитие концепции наследуемости от модульности до объектной ориентации, систематическое сопоставление концепции параллелизма в Аде и Оккаме-2, и др.). Для всех, серьёзно интересующихся программированием, в том числе научных работников, программистов, преподавателей и студентов.
Это, я так понимаю, тот самый глобальный обработчик, которым не комильфо рулить из библиотек, но и оставлять как есть не хочется.
Какого-то компромисса здесь не просматривается. Архитекторы Rust, мне кажется, промахнулись, пытаясь нащупать таковой. Чтоб самый обычный плюс и минус, которые везде, были спусковыми крючками апокалипсиса, ну такое. Мне кажется, потенциальным программистам на Rust надо лучше объяснять, в какую петлю они суются.
Работа по отмыванию устаревшего кода от грязи начинается с того, что переписыванием фрагментов разработчик пытается устранить WILD совсем. Авторам удалось отмыть от грязи достаточно большие массивы устаревшего кода, и пока они это делали, они обогатили свой транслятор различной полезной семантикой. Чтоб, например, можно было в своём коде делать проверки, а в код, который ну никак нельзя поменять, передавать структуры без жирных указателей, есть расщепление структур.
Из всех проектов по отмыванию унаследованного кода от грязи это самый продвинутый. На пути отмывания от грязи следующим этапом после принуждения к инкапсуляции памяти, казалось бы, должен быть порт на Аду, но нет, Ada is not invented here. В качестве самодостаточного языка программирования, не для старого кода, а для нового, у них самоделка Deputy по тем же лекалам, что и CCured. Для полноты можно упомянуть.
С C++ подобную штуку, похоже, невозможно нормально скрестить, так как и шаблоны, и анализатор минимальной жирности входят в конфликт. Единственно работающие способы отмыть C++ от грязи я вижу в CHERI и Эльбрус2000, то есть, максимально жирные указатели для всего. В то время, как в CCured большую часть указателей автоматом удаётся облегчить, а дальше разработчик устраняет оставшиеся WILD, в C++ эта работа даже не может начаться. Писать на C++ это такое попадалово, что потом вовек не отмыться. К сожалению, никто (Cyclone и др. отмывальщики C) не пошёл другим путём, в сторону Objective-C. Там, похоже, таких проблем, как с C++, нет, и можно было бы наполнить экосистему безопасным кроссплатфоренным GUI и программами на нём.
Для надёжных языков программирования поднятый вопрос актуален, потому что каждый bzip2 с нуля быстро не перепишешь, а если подключишь неотмытый libbzip2, то через него-то тебя и ломанут. Надо исследовать возможности, как отмыть унаследованные библиотеки на Си перед тем, как подключать их в свой код на надёжном языке.
Но если вдаться в детали, в Delphi и Ada оно приводит к легко уловимому exception, а в Rust случается паника. А паника в Rust не то же что исключение. Для отлова паники можно использовать функцию, принимающую два замыкания, первое — что нужно сделать, второе — что сделать, если поймали панику. Кажется, это похоже на обработчик исключений, но нет, опять не угадали. Перед тем, как упасть вниз по стеку, до точки, где установлен перехват паник, ещё успевает нагадить в консоль обработчик паник. Насколько я понимаю, этот обработчик тоже можно отключить или заменить более молчаливой версией. В программе такое можно сделать, а в библиотеке? Нормально ли будет, если какая-то библиотека заменит глобальный обработчик паник? Или если будет полагаться на то, что он отключён, и преспокойно себе бросать и ловить паники.
Всё это похоже на Turbo Pascal образца конца 80х. Там тоже целочисленные переполнения и выход за границы диапазона были. НО ВАЖНО КАК ОНИ БЫЛИ. Там же не было структурной обработки исключений. Там было прерывание, прерывание можно было гипотетически перехватить. Структурной обработки исключений нет, поэтому что делать обработчику прерываний, интересный вопрос. Лучшее, что можно было придумать, это на SetJmp/LongJmp сделать что-то вроде SEH с RAII. Как и в случае Rust, не приходится ожидать, что это сделано в библиотеках.
Так что по факту этот механизм не работает, или работает отнюдь не в полную силу, не так, как в других языках программирования.
В первом языке проверка целочисленных переполнений и диапазонов. В первом языке открытые массивы (в терминах ISO Pascal согласованные массивы, conformant arrays). В первом языке отличные от чисел перечисления. В первом языке ARC существует пусть хотя бы на COM интерфейсах.
Но первый язык не развивают, а развивают второй язык, где объекты с ручным управлением памятью, свойства, события и прочая излишне распиаренная хелсберговщина.
Второй язык развивается и продвигается, а первый язык прозябает в стагнации и безвестности. Вот, например, как были со времён Турбо Паскаля проверки целочисленного переполнения и диапазонов, так и остались почти без изменений. Ну разве что Assert добавились. А что, больше проверять нечего стало? nil можно было бы не ломиться разыменовывать, чтоб упереться в EAccessViolation, а проверить. А ещё, как в Ada 2005, можно дать синтаксис, чтоб запретить nil. В Ada 2005 not null может стоять везде, хоть в record. Это возможно сделать, потому что в языке Ada есть три фичи, которые делают возможным и удобным обращаться с такими структурами данных.
За счёт этого компилятору Ады есть, к чему принудить разработчика. Если же в Delphi всего этого не было, то пропустив азы, сразу в мир надёжности не вскочишь, но хотя бы в аргументах функций можно было бы nil запрещать. Могли. Но не сделали.
Идём далее. В ISO Pascal есть такая возможность, как conformant array parameters. В языке Ada она специального имени не имеет, но unconstrained arrays, если их принимать формальным аргументом, так себя ведут. Существует изначально, с 1983го года. В языке Delphi это называется open arrays. Существует с 90х, версия Delphi 4 или около того. В C++ что-то похожее появилось только в C++20, std::span. Можно такие параметры из обычных массивов делать, можно из динамических. Можно передать по цепочке принятый формальный аргумент такого типа. А ещё можно сузить, можно передать поддиапазон значения. Дебилизм в Delphi состоит в том, что псевдофункция Slice, единственный способ скрафтить суженное значение, может обрезать справа, но не слева, в отличие от Copy, создающей новое значение. Какого-то обоснования такому положению вещей я не вижу. Манипулируя с указателями, я могу добиться того, чтобы в функцию-таки передать обрезанный с другой стороны подмассив, но тогда компилятор не вполне способен проверить, не было ли выхода за границы диапазона. Это выглядит, будто тёмный угол, в который давно никто не заглядывал.
Ещё по мелочи хотелось бы, чтоб Boolean(5) не работал, а бросал исключение.
У меня есть опыт, как я устраивался на работу, включал в проекте проверки ошибок, и каааак повсплывало. Даже то немногое, что есть в Delphi, показало себя очень полезным.
С Адой им не тягаться, но у производителя по разным фичам была фора лет в пятнадцать прежде, чем они дойдут даже до жирафов C++. Всё это время могли бы выезжать на этой волне. И даже когда доходят, могли бы на конференциях выступить, типа
Сложный, противоречивый язык программирования получается. Отличные заделы плюс безбожный слив со стороны производителя.
Языки программирования. Концепции и принципы
Кауфман В.Ш.
Есть опыт ознакомления?
Как насчёт освежить?