Почему в 80е все писали на Паскале (кто-то на СМ'ках, кто-то на PC, да даже и Mac — тоже изначально был на диалекте Паскаля написан), а уже в 90е — перестали? Во-многом — именно потому, что Модула-2/Оберон и так далее не позволяли использовать сущесвующий код, а C++ — позволял.
Устроят тот же бардак
Справедливости ради, Паскаль там был без присмотра Вирта. Вирт-то давно на Модулу перешёл, соответственно, каждый следующий Mac Pascal, когда уже есть Модула — это не его бардак.
И вот, что там, именно в Паскалях, без Модулы, творилось со слов того, кто пытался писать под каждый OpenVMS: p2ada.sourceforge.net/pascada.htm
И все вы, веб-разработчики: Выучите С или Rust или что-то в этом духе.
До C веб-разработчиков допускать не стоит в своей массе, а до Rust они и сами не дойдут (в своей массе).
Честно пытался сделать веб-приложение на Си, точнее, на C++. Не хотел, чтоб у меня программа тормозила как чёрт знает что на ровном месте и сосала батарею нетбука как вампир. Нашёл, на чём это возможно сделать: imGUI + SDL2. Какое-то время перестрадал в попытках это запустить, но-таки запустил. И вот, что обнаружил: в EmScripten, поскольку управление нужно возвращать постоянно в самый низ (ну это я и так знаю), в SDL2 под EmScripten не работает SDL_WaitEvent. Вместо этого есть только SDL_PollEvent, которую нужно дёргать из процедуры, которую настраивает emscripten_set_main_loop. И это единственный способ, я пытался найти EmScripten-специфичные альтернативы, чтоб подписаться на события и пробуждаться только когда события есть. Нету!
И вот, что получается. Если подписаться по умолчанию, то всё время вообще, когда CPU свободен, он рендерит кадр. Работает отзывчиво, да. Под 60fps. Но у CPU жрёт всё ядро при том, что необходимости в этом никакой нет, пока ничего не происходит. Хочется взять фомку и объяснить кое-кому разницу между 0% CPU и 100% CPU, и пояснить насчёт людей, которые выбрали C под Web, какую сторону в этом континууме они бы ожидали получить. Но ничего я не возьму. Всё бесплатно же. Или в интересах бизнеса. Нет такого государства, которое бы вкладывалось в программирование в интересах народа.
Где-то глубоко в кишках там настраивается куча обработчиков мыши, клавиатуры и прочего, и они тихонько подкладывают события в очередь SDL2 и больше никак об этом не докладывают. Не подкопаться. А поставишь пробуждения раз в секунду — жор CPU уменьшится, но будет слайдшоу по кадру в секунду. Впрочем, если 60 таких посекундных вкладок открыть — это будет то же, что одна, жрущая весь CPU. Angular, конечно, жрёт CPU как не в себя, но всё же не настолько плох, чтоб перестать справляться с 60 просто открытыми вкладками.
Можно попробовать всё же подкопаться и подписаться на те же самые события, но тогда нет гарантий, в каком порядке будут вызываться события. Может, сначала подкопанный обработчик вызовется, поглядит на пустую очередь событий SDL2 и успокоится. Там всё настолько сломано, что это просто невыносимо.
В том, что я видел с упором на безопасность и корректность, нет указателей. Либо глобально размещено, либо на стеке какой-то задачи. Вот в Ironsides, SPARK-верифицированном DNS-сервере нет указателей, потому что та версия SPARK, под которую он писался, их не поддерживает. Это не мешает ему использовать контейнеры. Просто это контейнеры такие специальные, которые параметризуются максимальной вместимостью и занимают сразу максимум места, а вместо указателей там индексы.
С точки зрения системного администратора управление ресурсами — это не когда программа решила, сколько ей ресурсов, а когда системный администратор решил, сколько программе ресурсов, и так оно и есть. Вот SPARK, безопасность, корректность — это в первую очередь для таких людей.
А если на один участок памяти может быть более одного указателя, статически не отождествимых с одним, то всё, тут уже понятно, что это какая-то другая область с расслабленными требованиями начинается.
Из не совсем runtime: protected может работать как рвлок. При этом на чтение со множественным доступом содержимое-таки действительно видно в режиме только чтение. Также может работать как монитор Хоара. При этом условные переменные сигналятся автоматом. Куча таких мелочей делают жизнь проще.
Из ParaSail собираются заимствовать уникальные указатели. Они семантически похожи на записи с дискриминантом, на них наконец-то удалось натравить хоть какую-то верификацию, а если что-то новое можно верифицировать, оно идёт в язык Ада и SPARK. Будучи экономическим фундаментом, SPARK — это обстоятельство непреодолимой силы. Разделяемые ссылки в этом смысле плохие, и в ISO стандарт не идут, а так в библиотеках пруд пруди.
wrapping в релизе был чем-то настолько немыслимым, настолько редким специальным случаем, что я этот вариант даже не принял к рассмотрению. Нет проверок на числах — нет чисел. Нет чисел — нет программы.
Те, кто приходят с других языков и по привычке смотрят, что там с указателями, смотрят не туда. Предполагается, что в ультраидиоматичных адских программах голые указатели вообще не проскакивают. А ресурсами управляют, например, контейнеры. Лежит в карте где-то запись, навёл на неё 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, не приходится ожидать, что это сделано в библиотеках.
Так что по факту этот механизм не работает, или работает отнюдь не в полную силу, не так, как в других языках программирования.
Языки есть с одним владельцем. Он там поливает территорию вокруг себя, делает благообразно. Но чужие там не ходят. А есть демократичные. Там больше одного производителя, но и анархия. Везде свои сильные и слабые стороны.
Справедливости ради, Паскаль там был без присмотра Вирта. Вирт-то давно на Модулу перешёл, соответственно, каждый следующий Mac Pascal, когда уже есть Модула — это не его бардак.
И вот, что там, именно в Паскалях, без Модулы, творилось со слов того, кто пытался писать под каждый OpenVMS: p2ada.sourceforge.net/pascada.htm
До C веб-разработчиков допускать не стоит в своей массе, а до Rust они и сами не дойдут (в своей массе).
Честно пытался сделать веб-приложение на Си, точнее, на C++. Не хотел, чтоб у меня программа тормозила как чёрт знает что на ровном месте и сосала батарею нетбука как вампир. Нашёл, на чём это возможно сделать: imGUI + SDL2. Какое-то время перестрадал в попытках это запустить, но-таки запустил. И вот, что обнаружил: в EmScripten, поскольку управление нужно возвращать постоянно в самый низ (ну это я и так знаю), в SDL2 под EmScripten не работает SDL_WaitEvent. Вместо этого есть только SDL_PollEvent, которую нужно дёргать из процедуры, которую настраивает emscripten_set_main_loop. И это единственный способ, я пытался найти EmScripten-специфичные альтернативы, чтоб подписаться на события и пробуждаться только когда события есть. Нету!
И вот, что получается. Если подписаться по умолчанию, то всё время вообще, когда CPU свободен, он рендерит кадр. Работает отзывчиво, да. Под 60fps. Но у CPU жрёт всё ядро при том, что необходимости в этом никакой нет, пока ничего не происходит. Хочется взять фомку и объяснить кое-кому разницу между 0% CPU и 100% CPU, и пояснить насчёт людей, которые выбрали C под Web, какую сторону в этом континууме они бы ожидали получить. Но ничего я не возьму. Всё бесплатно же. Или в интересах бизнеса. Нет такого государства, которое бы вкладывалось в программирование в интересах народа.
Где-то глубоко в кишках там настраивается куча обработчиков мыши, клавиатуры и прочего, и они тихонько подкладывают события в очередь SDL2 и больше никак об этом не докладывают. Не подкопаться. А поставишь пробуждения раз в секунду — жор CPU уменьшится, но будет слайдшоу по кадру в секунду. Впрочем, если 60 таких посекундных вкладок открыть — это будет то же, что одна, жрущая весь CPU. Angular, конечно, жрёт CPU как не в себя, но всё же не настолько плох, чтоб перестать справляться с 60 просто открытыми вкладками.
Можно попробовать всё же подкопаться и подписаться на те же самые события, но тогда нет гарантий, в каком порядке будут вызываться события. Может, сначала подкопанный обработчик вызовется, поглядит на пустую очередь событий SDL2 и успокоится. Там всё настолько сломано, что это просто невыносимо.
В том, что я видел с упором на безопасность и корректность, нет указателей. Либо глобально размещено, либо на стеке какой-то задачи. Вот в Ironsides, SPARK-верифицированном DNS-сервере нет указателей, потому что та версия SPARK, под которую он писался, их не поддерживает. Это не мешает ему использовать контейнеры. Просто это контейнеры такие специальные, которые параметризуются максимальной вместимостью и занимают сразу максимум места, а вместо указателей там индексы.
С точки зрения системного администратора управление ресурсами — это не когда программа решила, сколько ей ресурсов, а когда системный администратор решил, сколько программе ресурсов, и так оно и есть. Вот SPARK, безопасность, корректность — это в первую очередь для таких людей.
А если на один участок памяти может быть более одного указателя, статически не отождествимых с одним, то всё, тут уже понятно, что это какая-то другая область с расслабленными требованиями начинается.
Список дополнительных runtime проверок, определённых GNAT
Из не совсем runtime: protected может работать как рвлок. При этом на чтение со множественным доступом содержимое-таки действительно видно в режиме только чтение. Также может работать как монитор Хоара. При этом условные переменные сигналятся автоматом. Куча таких мелочей делают жизнь проще.
www.dmitry-kazakov.de/ada/components.htm#Objects_etc
www.adacore.com/gems/gem-100-reference-counting-in-ada-part-3-weak-references
Ещё реализацию видел у Вадима Годунко в Матрёшке, а сам я пользуюсь собственной, которая работает как мне надо.
Это, я так понимаю, тот самый глобальный обработчик, которым не комильфо рулить из библиотек, но и оставлять как есть не хочется.
Какого-то компромисса здесь не просматривается. Архитекторы 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, не приходится ожидать, что это сделано в библиотеках.
Так что по факту этот механизм не работает, или работает отнюдь не в полную силу, не так, как в других языках программирования.