Я не случайно привёл в пример не просто один хеш коммита, а целый конфиг сборки.
Допустим, у нас есть какой-то редактор этого конфига и проще сохранять/загружать его в json, чем парсить какой-нибудь config.h. Особенно если конфиг сложный, содержит вложенные структуры и т д
Или мы качаем конфиг с внешнего сервиса, где его меняют через веб интерфейс люди вообще далёкие от программирования на плюсах, а CI/CD запускает сборку бинарника по нему.
Да, это всё уже сейчас решается кодогенерацией. Но это надо писать скрипты под конкретный проект, усложнять сборку и т д. А теперь можно просто положить JSON и компилятор сам всё сделает. Разумеется, constexpr парсер json должен быть из какой-то библиотеки, а не руками его писать.
В том, что по ТЗ не нужна динамическая замена ресурсов.
Это может быть embedded, где файловой системы вообще нет и всё вкомпилировано в бинарник. Это может быть WASM, где такое тоже встречается. Это может быть портативный софт "всё в одном бинарнике". Динамическая погрузка ресурсов не бесплатна, не всегда возможна и не всегда нужна.
Просто нужно иметь возможность вкомпилировать в бинарник некие const данные, которые не будут меняться после сборки. Раньше надо было описывать такие данные в коде, тепеоь можно принять данные в любом удобном формате без дополнительных утилит, кроме компилятора.
Это может быть конфиг. Активированные в билде фичи, версия, хеш коммита и т д. Они не должны и не могут меняться после сборки.
Раньше для такого приходилось писать кодогенератор, теперь можно использовать для конфига JSON без предобработки, который гораздо проще редактировать из других скриптов, чем исходник.
Или это могут быть статические ресурсы, которые не предполагаются к рантайм замене. Раньше их так же парсили из константы, но в рантайме, а теперь можно распарсить в compile time.
То что вы не можете придумать зачем фича, это ещё не значит, что она не нужна.
JSON может гененироваться внешней утилитой во время сборки, приходить из другой системы или писаться коллегой, который вообще не умеет программировать на C++.
Допустим, бекэндеры описывают в JSON какую-то схему данных, а в нашем приложении автоматически генерируется нужные DTOшки под неё во время сборки.
Раньше требовалось писать отдельный кодогенератор, а теперь можно это провернуть средствами компилятора.
Все такие ошибки (несовместимость типов, отчутсвующие методы и т п) происходят в момент использования шаблона, причём именно того самого метода, где происходит ошибка, а не в момент описания.
Компилятор пытается создать конструктор копированиятолько в момент его вызова. А если конструктор не вызывался бы нигде в коде, то ошибки компиляции не было бы.
Обычно это нужно для гибкости, чтобы можно было делать опциональные фичи в шаблоне требующие определённый тип и пропадающие с другими.
Здесь накладывается ещё возможность частичной специализации. В общем случае конструктора копирования может не быть (он компилируется в некорректный код), но есть частная специализация для int где конструктор реализован иначе и валиден.
Стабильный ABI позволяет скачать готовые бинарники какого-нибудь Qt и никогда его не собирать руками.
Или собрать один раз руками, а потом использовать во всех проектах на этой машине.
Rust собирает с нуля каждый крейт в проекте. Инкрементальная сборка работает только в рамках одного проекта на одной машине. И ещё нельзя взять чужой результат инкрементальной сборки и самому не собирать тяжёлые библиотеки, а только свой код.
Зачем нужны форвард декларации на уровне языка? Если очень нужно, можно все необходимые метаданные функций и типов из модуля запихнуть в обьектный файл. Если исходник модуля не изменился с последней компиляции объектного файла, то читаем все декларации оттуда (реализации функций, очевидно, не читаем, они не нужны компилятору, только линкеру) и не парсим исходник. Получаются те же форвард декларации, но автогенерируемые и в машиночитаемом виде.
Впрочем, даже так не обязательно. Это даст выигрыш лишь если преобразование исходника в AST занимает существенное время от общего времени компиляции. У вас есть информация о том, что в Rust именно парсинг узкое место? Потому что если нет, то никто не заставляет обращаться к AST нодам реализации, если мы компилируем не этот модуль. Можно пробежаться по верхам AST и, опять же, собрать одни декларации. Оверхед лишь на парсинг.
Форвард декларации появились в те времена, когда компилятор не мог держать в памяти полное AST дерево программы, а то и даже полный исходный текст одного модуля держать не мог. И надо было уметь компилировать файл на ходу, не загружая его в память целиком.
Сейчас это не актуально.
Также в те времена появились языки типа C/C++, эксплуатирующие форвард декларации в синтаксисе - он без них становится неоднозначным.
Например, в C и C++ выражение
A*B;
может быть как и арифметической операцией, так и декларацией переменной - зависит от того был ли A обьявлен типом или переменной. То есть для корректной работы парсера, он должен заранее знать всё, что может встретить.
В Rust (и многих других языках) такой неоднозначности нет - декларации переменных всегда предварены ключевым словом let, имена типов всегда идут после двоеточия и т д. Можно распарсить исходник не имея представления о том, что есть тип, а что переменная.
Длинные строчки не влезают на экран (особенно в режиме diff). Плюс дублирующаяся информация создаёт визуальный шум. На фоне самоочевидной информации теряется сама бизнес-логика, которую обычно гораздо важнее проверить - с проверкой типов справится и компилятор, а вот корректность алгоритма он не проверит.
если тип явно не указан - это намекает, что тут не всё так просто, как кажется, и следует изучить как туда что-то ещё попасть может
Если это Java, то вы знаете, что всё просто и туда может прийти только один, пусть и не указанный явно, тип. Что-то может изменится разве что с обновлением того кода, откуда приходит значение (описание самой функции calculate), но если она что-то радикально другое начнёт возвращать, то скорее всего будет ошибка компиляции (по отсутствию методов или попытки передать в функцию ожидающую конкретный тип, а var для аргументов нельзя). А ещё у вас есть тесты, которые при радикальном изменении поведения возвращаемого значения упадут.
В отличии от языков с динамической типизацией, здесь вы уверены, что хоть тип и не указан, но он всегда один. Нет никаких граничных случаев, что в рантайме придёт то, к чему функция не готова в плане типа. Так что ломающее изменение легко ловится и компилятором, и самыми базовыми тестами.
Чтобы не писать MyCoolObject obj = new MyCoolObject(). Или MyCoolObject obj = (MyCoolObject) anotherObject.
В общем, где тип и так указан в той же строке.
Ещё может быть какая-то функция (возможно, не ваша вообще, а библиотечная) возвращающая длинный тип из кучи генериков. А тебе из этого типа нужна пара полей.
А нужно ли мне здесь знать точный тип возврата calculate? Алгоритм то и так понятен (что-то посчитали, вывели инфу о прошедшем времени и значение результата).
В момент написания кода IDE подскажет какие у него методы. CI/CD уведомит, что код компилируется (если, например, кто-то изменит тип возврата calculate и там больше не будет нужных методов). А на ревью проверяется в первую очередь общая логика.
В ТЗ был вывод времени и значения. Код выводит время и значение. Готово.
Если тип неудачный, то там и логика пострадает. Что придётся всё с переменной делать через одно место. Условно, искать значение в List перебором вместо выборки из Map.
Но это будет видно и без знания типов. "Ага, он зачем-то сделал цикл в цикле с полным перебором массива, но ведь это можно было избежать, используй он Map".
А если по алгоритму непонятно, что с типами что-то не то, то скорее всего с ними всё то.
Когда это вносили в Java, ссылались на Kotlin/Scala/Go. Все эти языки имеют статическую типизацию с опциональным выводом типов. На Python или JavaScript никто не ссылался.
Вы не можете после присваивания var результата функции calculate имеющей тип CalculationResult присвоить ей ничего, кроме другого CalculationResult.
Динамическая типизация означает, что одна и та же переменная может в разное время работы программы держать в себе разные значения разных несовместимых типов. А то что при этом не нужно прописывать её тип уже следствие. Нечего прописывать.
В Java/Kotlin/Scala/Rust/C++ не обязательно (где-то с рождения языка, где-то, как в Java и C++ после обновления стандарта) указывать типы переменных, но переменная имеет тот самый выведенный тип и после того как он выведен, в неё нельзя класть ничего другого.
Касательно var в Java это вопрос кодстайла. Так же как и фабрика абстрактных фабрик фабрик тоже будет ужасна с точки зрения понимания человека, но Java никак не мешает этому с первой же версии. Всё хорошо в меру.
Например, var хорошо заходит там, где тип уже упомянут в той же строке, типа каста или new. Нет нужды его дублировать ещё и в описании переменной.
Так у них лицензия от варгейминга, который имеет другую позицию по поводу политики.
Это примерно как тот кто организует серый импорт макбуков в РФ заключая контракт на поставку не будет бить себя в грудь о поддержке РФ, а будет уверять, что вообще в Казахстан их везёт, честно честно.
Те кто на стыке с международным бизнесом ограничены в открытой поддержке ВС РФ (при этом могут делать это не афишируя).
У нас теперь есть два сорта экстремистских организаций. Одни те, которые непосредственно ведут деятельность против РФ своими руками. И единственная причина им переводить деньги - хотеть поддерживать эту деятельность.
Главный "товар", который получает задонативший ВСУ или, прости Господи, ФБК - их профильная деятельность. Первые воюют с ВС РФ, вторые организовывали митинги и снимали видосики с критикой правительства РФ. Даже если продаётся какой-то товар этими организациями (типа каких-нибудь значков с символикой), то всем понятно, что никому эти товары не были бы нужны, если бы продавец не вёл свою основную деятельность (и в описании товаров, как правило, четко указано, что деньги с продажи пойдут туда то, на такую то деятельность). За донаты таким организациям много уголовных дел.
А есть, так сказать, экстремисты "второго сорта". Организации у которых основная профильная деятельность не политическая. Покупая танк в игре цель ты в первую очередь получаешь танк в игре, который полностью самодостаточный товар (и он не является средством пропаганды). Типичный пользователь мира танков не ожидает сделать таким образом что-то против интереса РФ, да и ему никто этого и не обещает (никаких баннеров "с каждого доната рубль уходит на настоящие Леопарды", более того, сама Леста утверждает, что из своей выручки как раз донатила ВС РФ). И вот попробуйте найти дела за такие покупки - их нет.
Точно так же как Инста запрещена уже сколько лет, а дел за её использование вне политических целей так и не появилось.
Если непонятно как работает закон (а он действительно не разделяет на бумаге эти два типа организаций), смотрите реальные дела по нему. Там будут нужные закономерности.
Ну вот, допустим, ты покупаешь макбук. Деньги за макбук идут от перекупа к перекупу и в конце концов в Apple. А Apple даёт рекламу своей продукции в числе в Instagram. А это значит платит деньги Meta. А Meta признана экстремистской. И это даже без учёта налогов, из которых финансируется военная помощь США.
Значит ли это, что покупатель макбука тоже должен получить по шее за экстремизм? Ладно, "конечные пользователи" исключены. А как насчёт перекупа, который макбук в Россию импортировал?
У Леста есть лицензионные договоры и обязательства. Какой вариант у неё был не подпадать под раздачу? Начать нарушать договора с Варгеймингом?
Это вообще очень странная практика признавать экстримистскими сообществами коммерческие организации.
Было бы логично признавать экстремистом конкретного человека, который принял решение что-то кому-то задонатить.
За неуплату налогов есть своя статья (кстати, вывод капитала на Кипр сам по себе не значит, что в России с этих денег не были уплаченны все необходимые налоги). А тут экстремизм.
Да, но теперь кодогенератор не требует установки отдельных скриптовых языков, усложнения пайплайна сборки и т д. Теперь он встроен в язык.
Я не случайно привёл в пример не просто один хеш коммита, а целый конфиг сборки.
Допустим, у нас есть какой-то редактор этого конфига и проще сохранять/загружать его в json, чем парсить какой-нибудь config.h. Особенно если конфиг сложный, содержит вложенные структуры и т д
Или мы качаем конфиг с внешнего сервиса, где его меняют через веб интерфейс люди вообще далёкие от программирования на плюсах, а CI/CD запускает сборку бинарника по нему.
Да, это всё уже сейчас решается кодогенерацией. Но это надо писать скрипты под конкретный проект, усложнять сборку и т д. А теперь можно просто положить JSON и компилятор сам всё сделает. Разумеется, constexpr парсер json должен быть из какой-то библиотеки, а не руками его писать.
В том, что по ТЗ не нужна динамическая замена ресурсов.
Это может быть embedded, где файловой системы вообще нет и всё вкомпилировано в бинарник. Это может быть WASM, где такое тоже встречается. Это может быть портативный софт "всё в одном бинарнике". Динамическая погрузка ресурсов не бесплатна, не всегда возможна и не всегда нужна.
Просто нужно иметь возможность вкомпилировать в бинарник некие const данные, которые не будут меняться после сборки. Раньше надо было описывать такие данные в коде, тепеоь можно принять данные в любом удобном формате без дополнительных утилит, кроме компилятора.
Это может быть конфиг. Активированные в билде фичи, версия, хеш коммита и т д. Они не должны и не могут меняться после сборки.
Раньше для такого приходилось писать кодогенератор, теперь можно использовать для конфига JSON без предобработки, который гораздо проще редактировать из других скриптов, чем исходник.
Или это могут быть статические ресурсы, которые не предполагаются к рантайм замене. Раньше их так же парсили из константы, но в рантайме, а теперь можно распарсить в compile time.
То что вы не можете придумать зачем фича, это ещё не значит, что она не нужна.
Его может вообще не быть на этапе исполнения программы, он становится не нужен после сборки.
JSON может гененироваться внешней утилитой во время сборки, приходить из другой системы или писаться коллегой, который вообще не умеет программировать на C++.
Допустим, бекэндеры описывают в JSON какую-то схему данных, а в нашем приложении автоматически генерируется нужные DTOшки под неё во время сборки.
Раньше требовалось писать отдельный кодогенератор, а теперь можно это провернуть средствами компилятора.
Потому что SFINAE.
Все такие ошибки (несовместимость типов, отчутсвующие методы и т п) происходят в момент использования шаблона, причём именно того самого метода, где происходит ошибка, а не в момент описания.
Компилятор пытается создать конструктор копированиятолько в момент его вызова. А если конструктор не вызывался бы нигде в коде, то ошибки компиляции не было бы.
Обычно это нужно для гибкости, чтобы можно было делать опциональные фичи в шаблоне требующие определённый тип и пропадающие с другими.
Здесь накладывается ещё возможность частичной специализации. В общем случае конструктора копирования может не быть (он компилируется в некорректный код), но есть частная специализация для int где конструктор реализован иначе и валиден.
Стабильный ABI позволяет скачать готовые бинарники какого-нибудь Qt и никогда его не собирать руками.
Или собрать один раз руками, а потом использовать во всех проектах на этой машине.
Rust собирает с нуля каждый крейт в проекте. Инкрементальная сборка работает только в рамках одного проекта на одной машине. И ещё нельзя взять чужой результат инкрементальной сборки и самому не собирать тяжёлые библиотеки, а только свой код.
Зачем нужны форвард декларации на уровне языка? Если очень нужно, можно все необходимые метаданные функций и типов из модуля запихнуть в обьектный файл. Если исходник модуля не изменился с последней компиляции объектного файла, то читаем все декларации оттуда (реализации функций, очевидно, не читаем, они не нужны компилятору, только линкеру) и не парсим исходник. Получаются те же форвард декларации, но автогенерируемые и в машиночитаемом виде.
Впрочем, даже так не обязательно. Это даст выигрыш лишь если преобразование исходника в AST занимает существенное время от общего времени компиляции. У вас есть информация о том, что в Rust именно парсинг узкое место? Потому что если нет, то никто не заставляет обращаться к AST нодам реализации, если мы компилируем не этот модуль. Можно пробежаться по верхам AST и, опять же, собрать одни декларации. Оверхед лишь на парсинг.
Форвард декларации появились в те времена, когда компилятор не мог держать в памяти полное AST дерево программы, а то и даже полный исходный текст одного модуля держать не мог. И надо было уметь компилировать файл на ходу, не загружая его в память целиком.
Сейчас это не актуально.
Также в те времена появились языки типа C/C++, эксплуатирующие форвард декларации в синтаксисе - он без них становится неоднозначным.
Например, в C и C++ выражение
A*B;
может быть как и арифметической операцией, так и декларацией переменной - зависит от того был ли A обьявлен типом или переменной. То есть для корректной работы парсера, он должен заранее знать всё, что может встретить.
В Rust (и многих других языках) такой неоднозначности нет - декларации переменных всегда предварены ключевым словом let, имена типов всегда идут после двоеточия и т д. Можно распарсить исходник не имея представления о том, что есть тип, а что переменная.
Длинные строчки не влезают на экран (особенно в режиме diff). Плюс дублирующаяся информация создаёт визуальный шум. На фоне самоочевидной информации теряется сама бизнес-логика, которую обычно гораздо важнее проверить - с проверкой типов справится и компилятор, а вот корректность алгоритма он не проверит.
Если это Java, то вы знаете, что всё просто и туда может прийти только один, пусть и не указанный явно, тип. Что-то может изменится разве что с обновлением того кода, откуда приходит значение (описание самой функции calculate), но если она что-то радикально другое начнёт возвращать, то скорее всего будет ошибка компиляции (по отсутствию методов или попытки передать в функцию ожидающую конкретный тип, а var для аргументов нельзя). А ещё у вас есть тесты, которые при радикальном изменении поведения возвращаемого значения упадут.
В отличии от языков с динамической типизацией, здесь вы уверены, что хоть тип и не указан, но он всегда один. Нет никаких граничных случаев, что в рантайме придёт то, к чему функция не готова в плане типа. Так что ломающее изменение легко ловится и компилятором, и самыми базовыми тестами.
Чтобы не писать MyCoolObject obj = new MyCoolObject(). Или MyCoolObject obj = (MyCoolObject) anotherObject.
В общем, где тип и так указан в той же строке.
Ещё может быть какая-то функция (возможно, не ваша вообще, а библиотечная) возвращающая длинный тип из кучи генериков. А тебе из этого типа нужна пара полей.
Но если подумать о примере автора.
var result = calculate();
System.out.println("Elapsed time: " + result.getElapsedTime());
System.out.println("Result: " + result.getValue());
А нужно ли мне здесь знать точный тип возврата calculate? Алгоритм то и так понятен (что-то посчитали, вывели инфу о прошедшем времени и значение результата).
В момент написания кода IDE подскажет какие у него методы. CI/CD уведомит, что код компилируется (если, например, кто-то изменит тип возврата calculate и там больше не будет нужных методов). А на ревью проверяется в первую очередь общая логика.
В ТЗ был вывод времени и значения. Код выводит время и значение. Готово.
Что значит неудачный тип переменной?
Если тип неудачный, то там и логика пострадает. Что придётся всё с переменной делать через одно место. Условно, искать значение в List перебором вместо выборки из Map.
Но это будет видно и без знания типов. "Ага, он зачем-то сделал цикл в цикле с полным перебором массива, но ведь это можно было избежать, используй он Map".
А если по алгоритму непонятно, что с типами что-то не то, то скорее всего с ними всё то.
Когда это вносили в Java, ссылались на Kotlin/Scala/Go. Все эти языки имеют статическую типизацию с опциональным выводом типов. На Python или JavaScript никто не ссылался.
В Java нет алиасов типов.
Получается, либо велосипедить свой Optional или Map (если по факту нужно вернуть именно это), либо так.
Вы не можете после присваивания var результата функции calculate имеющей тип CalculationResult присвоить ей ничего, кроме другого CalculationResult.
Динамическая типизация означает, что одна и та же переменная может в разное время работы программы держать в себе разные значения разных несовместимых типов. А то что при этом не нужно прописывать её тип уже следствие. Нечего прописывать.
В Java/Kotlin/Scala/Rust/C++ не обязательно (где-то с рождения языка, где-то, как в Java и C++ после обновления стандарта) указывать типы переменных, но переменная имеет тот самый выведенный тип и после того как он выведен, в неё нельзя класть ничего другого.
Касательно var в Java это вопрос кодстайла. Так же как и фабрика абстрактных фабрик фабрик тоже будет ужасна с точки зрения понимания человека, но Java никак не мешает этому с первой же версии. Всё хорошо в меру.
Например, var хорошо заходит там, где тип уже упомянут в той же строке, типа каста или new. Нет нужды его дублировать ещё и в описании переменной.
Так у них лицензия от варгейминга, который имеет другую позицию по поводу политики.
Это примерно как тот кто организует серый импорт макбуков в РФ заключая контракт на поставку не будет бить себя в грудь о поддержке РФ, а будет уверять, что вообще в Казахстан их везёт, честно честно.
Те кто на стыке с международным бизнесом ограничены в открытой поддержке ВС РФ (при этом могут делать это не афишируя).
Правоприменительная практика вполне объективна.
У нас теперь есть два сорта экстремистских организаций. Одни те, которые непосредственно ведут деятельность против РФ своими руками. И единственная причина им переводить деньги - хотеть поддерживать эту деятельность.
Главный "товар", который получает задонативший ВСУ или, прости Господи, ФБК - их профильная деятельность. Первые воюют с ВС РФ, вторые организовывали митинги и снимали видосики с критикой правительства РФ. Даже если продаётся какой-то товар этими организациями (типа каких-нибудь значков с символикой), то всем понятно, что никому эти товары не были бы нужны, если бы продавец не вёл свою основную деятельность (и в описании товаров, как правило, четко указано, что деньги с продажи пойдут туда то, на такую то деятельность). За донаты таким организациям много уголовных дел.
А есть, так сказать, экстремисты "второго сорта". Организации у которых основная профильная деятельность не политическая. Покупая танк в игре цель ты в первую очередь получаешь танк в игре, который полностью самодостаточный товар (и он не является средством пропаганды). Типичный пользователь мира танков не ожидает сделать таким образом что-то против интереса РФ, да и ему никто этого и не обещает (никаких баннеров "с каждого доната рубль уходит на настоящие Леопарды", более того, сама Леста утверждает, что из своей выручки как раз донатила ВС РФ). И вот попробуйте найти дела за такие покупки - их нет.
Точно так же как Инста запрещена уже сколько лет, а дел за её использование вне политических целей так и не появилось.
Если непонятно как работает закон (а он действительно не разделяет на бумаге эти два типа организаций), смотрите реальные дела по нему. Там будут нужные закономерности.
Так передала деньги не Леста, а Варгейминг. И это было самостоятельное решение её руководства.
Леста наоборот донатила ВС РФ, но не слишком афишировала, чтобы избежать санкций.
Ну вот, допустим, ты покупаешь макбук. Деньги за макбук идут от перекупа к перекупу и в конце концов в Apple. А Apple даёт рекламу своей продукции в числе в Instagram. А это значит платит деньги Meta. А Meta признана экстремистской. И это даже без учёта налогов, из которых финансируется военная помощь США.
Значит ли это, что покупатель макбука тоже должен получить по шее за экстремизм? Ладно, "конечные пользователи" исключены. А как насчёт перекупа, который макбук в Россию импортировал?
У Леста есть лицензионные договоры и обязательства. Какой вариант у неё был не подпадать под раздачу? Начать нарушать договора с Варгеймингом?
Это вообще очень странная практика признавать экстримистскими сообществами коммерческие организации.
Было бы логично признавать экстремистом конкретного человека, который принял решение что-то кому-то задонатить.
За неуплату налогов есть своя статья (кстати, вывод капитала на Кипр сам по себе не значит, что в России с этих денег не были уплаченны все необходимые налоги). А тут экстремизм.