Comments 313
The behavior is undefined if *this does not contain a value.
Разработчик обязан удовлетворить предусловия функции перед ее вызовом. В данном случае он обязан убедиться, что optional содержит значение, иначе получит неопределенное поведение. Как бы того ни хотел автор, но в любом языке программирования функции будут иметь предусловия, явные или неявные. Не все предусловия можно проверить. Не все проверки предусловий осуществимы за разумное время. Не все осуществимые проверки стоят того, чтобы быть выполненными. Сомневающихся прошу ознакомиться с данным документом. Всегда придется искать какой-то разумный компромисс. Язык C++ нацелен на максимальную производительность, поэтому написанные на нем библиотеки часто перекладывают на пользователя выполнение дополнительных проверок.
Хотя я не отрицаю, что современный C++ превратился в какого-то монстра и использовать его корректно очень и очень сложно, чтобы бросаться переносить существующие наработки, надо сначала создать хорошую замену C++. Языки с VM и сборщиком мусора не являются заменой по определению. Rust выглядит довольно перспективно, но тоже не без проблем.
Разработчик обязан удовлетворить предусловия функции перед ее вызовом.
Логично, но люди совершают и будут совершать ошибки. Даже если программистов начнут расстреливать за UB, то ситуация принципиально всё равно не изменится: ошибок, вероятно, станет всё-таки поменьше, но и цена на разработку взлетит.
И насчёт "нацеленности на максимальную производительность" тоже, вроде как, справедливо. Но если быть честным, то насколько замедлится софт написанный на С++, если там включить проверки в std::option и векторе? Гадать дело неблагодарное, но я что-то думаю, что далеко не везде это будет заметно.
В этом плане мне намного больше нравится подход раста, где по умолчанию такие проверки как раз есть. А если вдруг мы видим, что в этом конкретном месте упираемся в производительность, то всегда можно использовать "unchecked" версию соответствующего метода (и желательно обложиться тестами). Да, в С++ есть at
, но честно говоря не встречал чтобы им активно пользовались. Может мне не повезло, конечно. Но зачастую при выборе между доступом по индексу через квадратные скобки и "неуклюжим" вызовом get_unchecked
выберут первое. В общем, я за "правильные" умолчания.
Логично, но люди совершают и будут совершать ошибкито то я смотрю в безопасных питонах и жсах люди совсем никогда не допускают ошибок…
И насчёт «нацеленности на максимальную производительность» тоже, вроде как, справедливо. Но если быть честным, то насколько замедлится софт написанный на С++, если там включить проверки в std::option и векторе?тут не может быть «на полшишечки», либо яп нацелен на максимальную производительность, либо нет.
Да, в С++ есть at, но честно говоря не встречал чтобы им активно пользовались. Может мне не повезло, конечновам не повезло
то то я смотрю в безопасных питонах и жсах люди совсем никогда не допускают ошибок…Ошибок управления памятью всё же не совершают
Но это ещё не всё. Обратная сторона отсутствия возможности управления памятью — это существенное замедление работы в рантайме. Попробуйте распарсить XML, используя любую библиотеку Питона, а потом сделайте то же самое на С++ через библиотеку RapidXML++ — разница в производительности будет отличаться в сотни раз! Почему? Да потому, что в Питоне на каждую маленькую строчечку — имя атрибута, значение атрибута, имя ноды, содержание ноды — будет хоботня с памятью, а RapidXML++ выделяет большие куски памяти и освобождает её только в самом конце и сразу, что даёт самый быстрый в мире парсинг. И даже если обернуть RapidXML++ в Питон, то ничего это не изменит, потому что Питону всё равно для парсинга придётся создавать все эти маленькие строки-переменные, чтобы можно было сравнивать их значения с искомыми.
Например, попробуйте запустить процесс через multiprocessing, передав ему в качестве аргумента NumPy массив, который освободится сразу после запуска процесса.Это будет происходить в любых местах, когда управляют памятью несколько независимых систем.
И даже если обернуть RapidXML++ в Питон, то ничего это не изменит, потому что Питону всё равно для парсинга придётся создавать все эти маленькие строки-переменные, чтобы можно было сравнивать их значения с искомыми.RapidXML++ использует свой менеджер памяти? Если строки только сравниваются на равенство, то можно обойтись без копирования, используя смещения.
Это будет происходить в любых местах, когда управляют памятью несколько независимых системВ таком случае заберите свои слова обратно, потому что — вот Пайтон, вот я использую стандартную библиотеку, вот получаю мусор (в переводе на ваш словарь — «ошибку управления памятью»)
RapidXML++ использует свой менеджер памяти? Если строки только сравниваются на равенство, то можно обойтись без копирования, используя смещения.Действительно, RapidXML++ определяет свой менеджер памяти. Но чтобы доставить эти данные в Пайтон (мы ведь говорим о парсере, правильно?), то для каждого узла, атрибута, имени атрибута и т.д. придётся создать маленькие переменные строки, а Пайтон не умеет создавать переменные, не выделяя под них память. В итоге разница в скорости — стократная, лично замерял для всех доступных библиотек. А всего лишь надо было отказаться от хоботни с памятью.
В таком случае заберите свои слова обратно, потому что — вот Пайтон, вот я использую стандартную библиотекуВо-первых, это не стандартная библиотека. Во-вторых, данная ошибка вызвана кодом написанным на си, и исправить её переделав python код не получится.
придётся создать маленькие переменные строки, а Пайтон не умеет создавать переменные, не выделяя под них памятьВы говорите так, словно плюсы умеют работать с переменными, под которые не выделена память.
Во-первых, это не стандартная библиотекаОна идёт в стандартной поставке Python 3. Заходим сюда, замечаем заголовок: «Documentation » The Python Standard Library » Concurrent Execution». Ниже: «multiprocessing — Process-based parallelism»
Во-вторых, данная ошибка вызвана кодом написанным на сиСтандартный интерпретатор (ака cpython) вместе со стандартной библиотекой на треть написан на си (можете зайти на гитхаб и увидеть это в статистике. В частности, когда вы работаете со строками или списками, то вызываются си функции.
Вы говорите так, словно плюсы умеют работать с переменными, под которые не выделена память.Мой поинт был в том, что в Пайтоне хоть убейся, но не сможешь управлять памятью эффективно. Иногда это приводит к тому, что ты не можешь даже XML распарсить за вменяемое время. С другой стороны, в С/С++ ты можешь создать свой эффективный менеджер памяти (RapidXML++) и даже выделить переменное количество памяти на стэке (alloca()), не дёрнув ни один пайтоновский или системный вызов.
Она идёт в стандартной поставке Python 3.Вы говорите про multiprocessing, я говорю про NumPy.
Мой поинт был в том, что в Пайтоне хоть убейся, но не сможешь управлять памятью эффективно.Кроме сборки мусора, между ними есть и другие различия, такие как типизация и интерпретация/компиляция. И если убрать эти различия, то преимущества ручного управления памятью либо уменьшаться, либо, в некоторых случаях исчезнут. Вот пример похожей задачи. Можно заметить, насколько сильно различаются даже задачи на одном языке, когда C++/g++ (Boost.PropertyTree) проигрывает как Ruby, так и другой реализации на C++. Как видите, некоторые реализации со сборкой мусора могут быть достаточно быстрыми.
Вы говорите про multiprocessing, я говорю про NumPy.Ну хорошо, допустим — мне лень проверять этот случай для не-NumPy. Но ведь в основе переменных NumPy лежит тот же reference counter, который забирается сборщиком мусора, когда на переменную никто не ссылается. Почему же Python multiprocessing не сохранил ссылку на передаваемый аргумент и позволил сборщику мусора забрать данные, которые нужны другому процессу?
Попробую и тут выкрутиться ) «Ошибок управления памятью всё же не совершают» — так вы хотите сказать, что это утверждение верно только для стандартной библиотеки? Интересно, ведь одно из самых больших преимуществ Пайтона именно в том, что у него полно сторонних библиотек на все случаи жизни. OpenCV, TensorFlow, Panda3d, NumPy, SymPy, Matplotlib etc.
И если убрать эти различия, то преимущества ручного управления памятью либо уменьшаться, либо, в некоторых случаях исчезнутВозможность ручного управления памятью — это вообще одно из главных преимуществ С++. В частности, в геймдеве и реал-тайм системах. Для бизнес-проектов мне сложно представить зачем использовать С++.
«Ошибок управления памятью всё же не совершают» — так вы хотите сказать, что это утверждение верно только для стандартной библиотеки?Нет, это верно для кода, который полагается на сборку мусора, то есть того кода, который не вызывает free и вариации либо по той причине что не может — сам python, либо так как незачем — c.
Почему же Python multiprocessing не сохранил ссылку на передаваемый аргумент и позволил сборщику мусора забрать данные, которые нужны другому процессу?Поскольку компилятор не проверяет код интерпретатора и библиотек на ошибки, а код достаточно нетривиален.
В ~25 раз медленнее быстрой C++-реализации - это "достаточно быстро"?
Как видите, некоторые реализации со сборкой мусора могут быть достаточно быстрыми.
Так вроде речь шла не о сборке мусора в целом, а о конкретной реализации, где память из кучи выделяется под каждый элемент, а не, скажем, блоками сразу по *цать элементов.
Зависит от языка.
Я конечно далеко не эксперт в разных языках и парадигмах программирования, но тем не менее с этим утверждением я не согласен. Предусловия бывают самые разные, явные и неявные (подразумевающиеся). Как вы проверите, что вам передали указатель на последовательность символов, оканчивающуюся нулем? Если вызывающий код забудет дописать терминирующий ноль, вы этот факт никак не установите. Как вы проверите, что переданный в функцию указатель на структуру действительно указывает на такую структуру? Rust может гарантировать нечто подобное, но если данные пришли из внешнего источника их все равно придется валидировать. Любой самый защищенный язык при работе с низкоуровневыми абстракциями будет вынужден использовать unsafe секции кода. И там будут непроверяемые или сложнопроверяемые предусловия.
А теперь убираем из этой схемы завтипы и ничего не меняется :)
А зачем вы уберете проверку?
Очень странно у вас проходит рефакторинг.
У меня обычно бывает противоположная проблема, когда нужно быстро проверить к-нить гипотезу, но вместо этого приходится полдня распутывать неудачно спроектированные типы
Если уж так волнуетесь за сохранность проверки при оефакторинге, пишете тест до рефакторинга и запускаете после
А если данные пришли извне?Либо это обеспечивается компилятором другого языка и вопрос только в передаче данных между языками, либо есть вероятность, что код формирующий эти данные уже успел наломать дров.
Потребую доказательство, что последний элемент массива — ноль. Это как раз просто.А почему именно последний? Очень вероятно, что массив не будет заполнен полностью и ноль окажется где-то в середине, например — получение пакета по сети, обмен не обязятельно ведётся по 1500 байт.
Однако, обсуждаемый вопрос об operator* у std::optional к общению со внешним миром отношения не имеет.Запросто, например, получение сетевого пакета без задержек, ситуацию моделирую тут, но почему бы и нет
std::optional<std::string> recv_str = server.recv();
if( recv_str ) { ...
} else { ...
}
Это пример неудачно разработанного API: часто используемая функция, могущая вызвать UB при случайном неправильном применении. Очень похожий тип есть в Rust и называется Option
, но там он свободен от подобной проблемы: у него нет оператора разыменования. Есть либо извлечение с явной проверкой:
let x: Option<i32> = None;
if let Some(y) = x {
либо преобразование Option
в другой Option
через map:
let y = x.map(|v| v+1);
либо "разыменование", но с паникой (в C++ это могло бы быть исключение) в случае его некорректности:
let x: Option<i32> = None;
let y = x.expect("Bug: x contains no value");
Такой API гарантирует, что при некорректном использовании будет не UB, а всего лишь корректная runtime error. Сама вероятность некорректного использования также понижается.
Меня удивляет, что у std::optional
в C++ нет ничего похожего на Option::map
/Option::and_then
. Вроде ж максимально логичные методы.
В расте может быть unsafe unwrap_unchecked()
Можно, но этот метод надо в unsafe блок заворачивать. В обычном коде никто так не пишет (хотя бы из-за того, что набирать дольше). Ну а если кровь из носу надо, то да — можно. И это хороший подход так как unsafe и на ревью сразу видно и грепнуть можно или даже запретить на уровне модуля/библиотеки.
Сравните тот же Option из раста. Оно гарантирует либо панику, либо значение. В C++ в лучшем случае будет исключение, в худшем кто-то получит доступ к вашей памяти. То бишь не гарантирует практически ничего, кроме почти безопасного интерфейса. Об это и ведет речь автор. Пока не заведутся полноценные контракты из нового стандарта безопасность приложений останется под угрозой эксплуатирования таких проблем с памятью. Но и контракты не панацея и ждать их в лучшем случае лет 5.
Хорошо бы контракты оформлять в типы. В правильной реализации у вас был бы класс SortedArray с непубличным конструктором и возможностью создать объект этого класса через вызов или sort (отсортировать имеющийся массив), или fromSortedArray (с проверкой, что переданный массив уже отсортирован). И, естественно, доступ к элементам такого массива возможен только на чтение.
То, что С++ идет фиг знает куда в попытках завернуть в красивые обертки как можно больше кода и вывести этот код из под контроля разработчика — это верно. С++ по своей структуре провоцирует на бездумное использование стандартных библиотек в надежде что «оно все само разрулит».
В чистом С все было честно — вот вам очень острый ножик, им можно делать что угодно, но легко порезаться если делать это без ума. В С++ ножик по прежнему остр, но эта острота тщательно скрыта от разработчика.
Предлагается не связывать ножи и вилки вместе в разные стороны. А так же не снимать с лезвия рукоятку.
Мда, за предложение сделать без уязвимостей
Сложные проблемы всегда имеют простые, легкие для понимания неправильные решения (С) сборник законов Мерфи, если не ошибаюсь. Впрочем, очередная статья про внезапный «нибизапастный сиплюсплюс», не знаю, стоило ли ее переводить — разве что в качестве затравки для очередного срача.
Да и когда кому мешал хороший технический срач? =)
На rustsec загляните тоже между делом — при всей мизерности объемов существующего кода на расте уязвимости типа double-free/use-after-free и прочий букет там находят регулярно, в том числе связанные с нарушением контрактов. Это не сферический «всегда-safe-код-в-вакууме» из мечт апологетов раста, а то, что обычный средний разработчик будет писать на расте в реальности. А вы, извините, как филин из известного анекдота со стратежным советом «надо, чтобы компилятор думал за человека, тогда все будет хорошо». Компиляторы тоже пишут люди.
Ладно, этот срач уже не первый (и по-видимому не последний), все аргументы всех сторон известны наперед, не вижу смысла в дальнейшем участии в очередной итерации :) Займусь чем-нибудь более полезным, поправлю вон очередной баг в fheroes2.
«it is not about the language itself that makes it any more or less secure, but how you use it»Ну так Раст как раз и заточен на то, чтобы не было зависимости от but how you use it.
Надо срочно менять и эти языки на что-то другое? А поможет ли это? Что-то я сомневаюсь.Вообще-то да. Например, php в поставке по умолчанию, уязвим к загрузке файлов на сервер, и для обхода врождённой проблемы могут потребоваться значительные усилия, так-как сам язык подразумевает вызов кода по имени файла напрямую.
XSS возможен, так как языки работают с ответом как со строкой, безо всякой проверки корректности. Будь ответ представлен не как строковой буфер, а как дерево, пускай даже с лёгкой возможность вставки сырых строк, то это уже существенно бы облегчило проверку кода, и снизило количество ошибок. В случае, если бы для использования сырых строк дополнительно проверялось бы компилятором, как например в rust, то эти ошибки стали бы ещё более редкими, так как новички не смогли бы элементарно пройти проверку валидации. Например, следующий код не скомпилируется
fn main() {
let t = "Hello, world!";
println!(t);
}
Это не сферический «всегда-safe-код-в-вакууме» из мечт апологетов раста, а то, что обычный средний разработчик будет писать на расте в реальности.
#![forbid(unsafe_code)]
в корне проекта — и у вас в команде никто unsafe-ом дров не наломает.
приходится использовать всякие ухищрения типа буфера + спанов в него, на расте без unsafe я такое в принципе бы не смог написать.
Так, а вот про это можно поподробнее?
Так, а вот про это можно поподробнее?ну в плюсах я спокойно могу написать и использовать что-то в духе такого минимального примера:
struct Foo {
Foo(std::string s) :
str(std::move(s)),
view(str) // ссылка на тот же буфер, что и в str
{}
std::string str;
std::string_view view;
};
Как бы с одной стороны да, с другой — я бы на ревью подобный код не пропустил бы, поскольку тут view
очень легко может повиснуть.
Как бы с одной стороны да, с другой — я бы на ревью подобный код не пропустил бы, поскольку тут view очень легко может повиснуть.А с какой целью вы ревьюите код? Проверить его на наличие ошибок или завернуть по подозрению, которое вам лень перепроверять?
Мне лень высматривать весь код, чтобы убедиться в том, что в нём нет операций, которые меняют str
, и я глубоко сомневаюсь, что люди, которые с этим кодом будут работать в дальнейшем, будут помнить, что это самоссылающаяся структура. Даже просто поменять тип str
на const std::string
было бы лучше.
И, кстати, конкретно в этом коде я вообще не вижу смысла в поле view
. Оно и так будет инвалидировано при любом операции, затрагивающей str
, а создать string_view
из std::string
можно в любой момент, это очень дешёвая операция. Так зачем его вообще хранить?
Мне лень высматривать весь кодпредставьте что вы реализовали какую-то нетривиальную функциональность, выложили код на ревью, и получаете от коллеги отзыв «я не вчитывался конечно, но мне кажется что ты где-то мог накосячить, переделай». Понравится?
Даже просто поменять тип str на const std::string было бы лучше.ну я так и сделал по итогу
И, кстати, конкретно в этом коде я вообще не вижу смысла в поле viewну это же сильно упрощенный пример, в реальном коде куча вьюшек на разные диапазоны внутри буфера.
И я считаю, что это делает мой код (и статью) лучше, чище и понятнее.логично, ведь задача ревью как раз улучшить код, который попадет в базу. Вот замечания с предложениями (нераздельно!) эту цель преследуют. Или даже вопросы, иногда они действительно указывают на неочевидные моменты. А вот задачи «не допустить код» у код ревью нет и никогда не будет — код ведь с какой-то целью пишется, верно?
И когда я вижу комментарии а-ля «я бы на ревью подобный код не пропустил бы» и «мне лень высматривать весь код, чтобы убедиться...» я делаю вывод, что у человека синдром вахтера и ЧСВ.
И когда я вижу комментарии а-ля «я бы на ревью подобный код не пропустил бы» и «мне лень высматривать весь код, чтобы убедиться...» я делаю вывод, что у человека синдром вахтера и ЧСВ.
То есть недопущение в кодовую базу кода, корректное использование которого требует нелокального соблюдения важного инварианта, который нельзя зафорсить компилятором и потому почти гарантированно создаст проблемы при сопровождении — это ЧСВ?
То есть недопущение в кодовую базу кода,… — это ЧСВ?нет, это синдром вахтера. ЧСВ вы проявили в «мне лень проверять, я проще заверну» — я не могу воспринимать это иначе как неуважение к коллегам.
корректное использование которого требует нелокального соблюдения важного инвариантаэто вы из головы взяли, у меня вся логика под капотом весьма простого API.
почти гарантированно создаст проблемы при сопровождениилюбой нетривиальный код, решающий нетривиальную задачу, вызовет проблемы при сопровождении, если разработчики не будут даже пытаться разобраться в коде. Напомню, что наша дискуссия началась с фразы «пишу библиотеку с кучей требований по перфу и памяти, приходится использовать всякие ухищрения».
Продолжим придумывание аналогий. Автор предлагает установить в гоночный болид подушки безопасности
Ну да, немного неправильную аналогию привел. Автор предлагает установить подушки безопасности ВО ВСЕ автомобили, включая гоночные болиды
Причем подушки безопасности конкретно одного производителя
Мне кажется, что популярность раста обеспечивается, в том числе, что у него порог входа относительно низкий. Да, язык совсем не простой, но сравнимый с С++, на котором пишет множество людей. В чём-то сложнее, в чём-то проще. Так вот если плюсы принято ругать за переусложнённость, то хаскаля в среднем боятся ещё больше. И это "всего лишь" хаскель.
Кстати, что есть мощное и безопасное без сборки мусора? Agda и ATS? Да, где-то применяются, но даже относительно раста этого не особо видно. Может, конечно, не там смотрю. Так-то я надеюсь, что индустрия будет двигаться в эту сторону, но пока это всё весьма далеко от мейнстрима.
Кстати, что есть мощное и безопасное без сборки мусора? Agda и ATS?Навскидку, Ada, BetterC, Swift, Zig.
Раст я списал в утиль. Металлолом, чё.
Ada
Бесполезна без SPARC. И да, какой там профиль Ravenscar выбирать?
BetterC
Который не является самостоятельным языком, а режимом компилятора D и на котором львиная доля библиотек не скомпилируется.
Swift
Который вставляет подсчёт ссылок для каждого класса? Не смешите меня.
Zig
Который базируется на принципе "программист знает, чё делает", так же, как и C.
Я ответил на конкретно заданный вопрос.
Кстати, есть статья, в которой анализируется надежность Zig в сравнении с Rust. Небольшое преимущество как по мне.
Да и в остальном я не очень то согласен. Можно критичные к надежности участки программ писать на безопасном подмножестве, а типичный бойлерплейт — на удобном языке.
Из перечисленного под критерий "мощное и безопасное" подходит только ада. Свифт — приятный язык, но в чём его мощь? Да и безусловный подсчёт ссылок действительно не всегда подходит для системного (а иначем зачем от сборки мусора отказываться?) языка.
Статью про Zig читал, по моему, там основной упор как раз на удобство. Разумеется, это тоже важно, но не в контексте про "мощность и безопасность". Перечитаю, может подзабыл что-то.
Ну и начинать проект на D или Zig я бы побоялся. С растом в этом плане тоже не всё так хорошо, но мы людей успешно находили.
Раст я списал в утиль. Металлолом, чё.
Объективно, ничего не скажешь.
Ну и начинать проект на D или Zig я бы побоялся.Я бы тоже. Конкретнее, я даже С++11 побоялся в своей конторе — типичные люди не тянут, выбрал С#. Через 13лет видны конечно и недостатки такого выбора.
Объективно, ничего не скажешь.Раст не годится для энтерпрайза — птичий, сложный и неудобный язык. Надеюсь, найдет свою нишу. Где-нибудь в драйверах.
Раст не годится для энтерпрайза — птичий, сложный и неудобный язык.
С "неудобный" я бы поспорил. Ни в одном мейнстримном ЯП нету сумм-типов. О каком удобстве вообще может идти речь?
Это другая статья про Zig, не та что я переводил.
Да нет, мне именно статья по ссылке попадалась. Правда я её скорее пролистал, так что от перевода не отказался бы. (:
выбрал С#. Через 13лет видны конечно и недостатки такого выбора.
Было бы весьма любопытно послушать подробности.
Раст не годится для энтерпрайза — птичий, сложный и неудобный язык.
Что ж, могу порадоваться, что я не энтерпрайзом занимаюсь.
Лично я из языков, дающих сопоставимые с Rust гарантии, и не требующих рантайма, знаком только с ATS, и он не для людей.
Но ведь в расте подушки как раз можно отключить. А для среднего водителя пусть всё-таки будут включены по умолчанию.
У встроенных проверок есть одна неприятная особенность. Они могут неудовлетворять имеющейся инфраструктуре. Например, есть метод at, который бросает исключение. Но какое исключение? Есть ли в этом исключении нормальное описание проблемы? Stacktrace? А проект все это требует
То есть громкое падение (я уже не говорю об UB) инфраструктуре удовлетворяет, а исключения (которые можно перехватить и обработать должным образом) нет?
Когда я смотрю на исключение, выванное at, я понимаю, что лучше бы он упал с нормальным core dump-ом.
Все эти обработки ошибок внутри блиотечных функций никак не уменьшают работу программиста на написание надежного (с точки зрения требований его компании) кода. Потому что мы не можем полагаться на то, что вылетит из функции
Автор предлагает ездить на машинах с подушками безопасности и специальным образом собранным кузовом, который при столкновении сминается в правильных местах, а не где водитель.
Нет. Речь идет именно об инструментарии для создании чего-то нового, а не о конечном потребительском продукте.
Так что автор предлагает именно специальные режущие инструменты, которыми невозможно порезаться. Пластиковые.
Да, ошибок use-after-free, переполнений буфера и тому подобного в коде на С не бывает. И в линуксе такого не было, и в openssl не было, и нигде не было.
Было. Но это было следствие невнимательности или низкой квалификации разработчика. И никто не скрывал что это возможно и что требуется думать о том, как ты распределяешь память и как с ней работаешь (в том числе). Т.е. от разработчика требовалась некоторая квалификация, а не просто прослушать пару бесплатных курсов на трубе.
А автор предлагает некий «язык для даунов» который поправит все ошибки, сам освободит память и т.п. Задача разработчика — выбрать правильный фреймворк и собрать из кубиков очередное поделие. А коль юзер начнет жаловаться что все тормозит, попенять ему что у того железо слабое.
Это и есть стиль «херак и в продакшен». Быстро и эффективно. А там трава не расти.
Точно не над openssl, точно не над ядром линукса
Как вы лихо подменили отрицание квантора всеобщности на всеобщность отрицания
Ну видимо их нет. А что вы предлагаете? Я как-то спрашивал у вас, возможно ли задать какую-нибудь базу на идрис коде, чтобы менее опытные разработчики могли построить приложение, но как я понял, это невозможно
Дык я вроде и не хвалю. C++ — ужасный язык
Ну так зачем хвалить С++, аппелируя к тому, как он хорош в руках программистов, которых не бывает?странная логика. Крупнейшие современные софтовые проекты написаны на плюсах и на си (браузеры, компиляторы, виртуальные машины, ОСи...), и пара багов помножает на ноль квалификацию их авторов? То есть если я зайду в багтрекер какого-нибудь rust-проекта и увижу там некоторое количество тикетов, я тоже могу сказать что на нём невозможно разрабатывать, видите ли потому что баги бывают?
Не пара, а сильно большетак вы оценивайте число багов относительно размера проекта. Так то понятно что в средней раст тулзе багов будет меньше, чем в хроме, но это потому, что в ней даже строчек столько не наберется.
и, вообще говоря, топовых специалистов в области.вы уж определитесь, «топовых специалистов в области» или «хороших с++ программистов не бывает»? А то походу квалификация среднего программиста на с++ зависит только от приводимого в споре аргумента
Вопрос в том, без багов какого рода невозможно разрабатыватьопять — как плюсы, так невозможно без багов разрабатывать. А как раст, так никто никогда в unsafe не ошибается, или я не понимаю и это другое?
Одно другому не противоречит. Топовые программисты на C++ всё равно могут допускать (и допускают) ошибки в значимом количествеа «в значительном количестве» это сколько? Вот например взять среднего питониста и среднего плюсовика, замерить errors/kLOC, и какие числа получатся? Просто если вы так уверены в достоверности своих тэзисов, наверняка у вас есть эксперименты, способные их подкрепить? В конце концов, должно же быть что-то объективнее чем «а вон в хроме по ошибке в пару дней», учитывая что там дифф/час измеряется тысячами строк.
А как раст, так никто никогда в unsafe не ошибается, или я не понимаю и это другое?
Вот только unsafe будет локализован и в нормальных проектах его стараются минимизировать. А в С++ (грубого говоря) — весь код unsafe.
Ну, если сможете показать как и чем это поможет, то будет круто. Вопрос то в простоте и массовости. И на c++ можно нормально писать, если знать как
Вы о каком-то конкретном примере?
Я о том как писать код собственно.
У меня есть сомнения, что написание безопасного и корректного софта — простое и массовое занятие с какими угодно технологиями
У меня тоже. О чем тогда спор?
Просто мало кто знает.
Это и называется опыт.
Ну и если эта самая внимательность у вас бесконечная.
Почему во всех утверждениях аппелируете к абсолютной внимательности? Никого она не волнует, а вас волнует
О том, что с некоторыми инструментами это всё же делать проще, чем с другими
Да, проще. Может быть. Чуть-чуть. Но не на порядок. Правильные "паттерны" (паттерны не в том смысле, как подумали) дают выигрыш сильно больше.
могу забыть неявные предположения,
Да хоть комментарий можно добавить, я не знаю
Вот только если бы эти комментарии машина бы как-то проверяла и сама ругалась, если они либо устаревают, либо вы им не следуете…
Ну можно давать нормальные имена переменных, ставить проверки, писать тесты… Альтернатива — завтипы, но насколько они лучше всего перечисленного? Я так и не понял, какую проблему они решают, за исключением специфично-математических
for(var i = 0; i < a.length; i++) {
f(a, i);
}
function f(a, i) {
a[i] = 0;
}
Без зависимых типов, нам нужно либо в теле функции f проверять выход за пределы массива руками, либо во время выполнения будет ошибка, если кто-то передаст неверный индекс. Зависимые типы позволяют убрать проверки, доказав, что индекс находится в нужных пределах.function f(a, i) {
a[i+1] = 0;
}
И второе, нам же надо либо верифицировать всю программу и тогда она будет написана примерно никогда, либо верифицировать только критически важные моменты, но так как это человек решает что критически важно а что нет, то он снова ошибается. А даже если не ошибается и верно опеределил все критически важные моменты, то 90% работы он уже сделал, надо более внимательно их обработать. Разве нет?
Но в данном примере что даст проверка на верный индекс при вызове функции?Нет. Проверяется индексация и она поднимается по стеку вызова. Каждое действие с i всего лишь смещает диапазон.
То есть в моём примере это
-1 < i < a.length
, в вашем -2 < i < a.length - 1
И второе, нам же надо либо верифицировать всю программу и тогда она будет написана примерно никогда, либо верифицировать только критически важные моментыВ идеале, абсолютно внимательный программист на си и так не забывает ставить эти проверки. Проверять придётся далеко не каждую строку, например, мой пример выше соберётся без изменений, просто потому, что компилятор может доказать его самостоятельно. Если же речь идёт об уже написанном коде, то никто не мешает внедрять проверку постепенно, шаг за шагом.
А можно так
for(var i = 0; i < a.length; i++) {
f(a[i], i);
}
Тогда никакие проверки в f не нужны вообще
Тогда нужен более конкретный пример. Не вижу, что еще обсуждать в данном примере
В примере выше я обошолся без завтипов. Несите другой
Нужно смотреть конкретный пример, а не гадать почем зря
Ну посмотрел, не нашел ничего, что нельзя было бы покрыть обычными типами с проверкой в конструкторе. Да и тесты никто не отменял, чтоже в них плохого?
А вот вы скажите мне одну вещь. В этой статье рассматривается пример деления на 0. И для этого вводится специальный тип, который заставляет проверять, является ли делитель нулем. Но (не знаю, так ли в haskell) исключительной также является ситуация деления maxint на -1. Где это учитывается? Автор об этом забыл?
Ну посмотрел, не нашел ничего, что нельзя было бы покрыть обычными типами с проверкой в конструкторе.Надёжность получается как у интерпретируемых языков — до тех пор, пока данная ветвь кода не выполниться, о существовании ошибки знать никто не будет.
Да и тесты никто не отменял, чтоже в них плохого?Вы будете писать тесты каждому, кто скачает вашу библиотеку? Если вы пишите код в команде, то вы непрерывно отслеживаете кто и с какими аргументами вызывает ваши функции?
Но (не знаю, так ли в haskell) исключительной также является ситуация деления maxint на -1. Где это учитывается?
ghci
GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
Prelude> :t 5 / 7
5 / 7 :: Fractional a => a
Prelude> 5 / 7
0.7142857142857143
Prelude>
а попробуйте удалить доказательство в какой-нибудь функции посередине
Но это в том числе усложняет прототипирование, когда нужно 2 дня разбираться в иерархии классов вместо написания кода.
Что за их полнотой, корректностью и актуальностью никто не следит
Просто для того, чтобы за ними следить, нужно терпение, которого не хватает и на завтипы
Но это в том числе усложняет прототипирование, когда нужно 2 дня разбираться в иерархии классов вместо написания кода.И это как раз та причина, когда к коду на си/плюсах добавляют скриптовый движок, а то как-то слишком сложно и собирается долго.
А для прототипирования есть специальные методы, которые позволяют обходить проверки, например unwrap для option, если что-то пойдёт не так, то программа безопасно упадёт.
Не понял, при чем тут долго собирается.
И как потом убирать все unwrap из кода? Плюс хорошо, когда они есть.
И как потом убирать все unwrap из кода?Как обычно убирают костыли — переписать небольшие фрагменты. В случае rust, это будет либо match, либо if let. Возможно потребуется дописать какой-то код, вроде сообщения пользователю, что он сделал что-то не то, например ввёл строку вместо числа.
Это понятно. Я имею в виду как найти все такие места. И что делать с типами у которых unwrap метода нет
Есть инструментарий, который помогает поймать все вызовы panic!
. Все методы типа unwrap и прочие, которые просто прерываю выполнение программы в конечном итоге вызывают panic!
.
Метод типа unwrap нужен для очень ограниченного подмножества типов. То есть при преобразовании строки в число у вас будет точно такой же Option, как и в куче других случаев.
Если взять другой язык, например Crystal, то там будет ещё проще.
a = some_condition ? nil : 3
if a.nil?
# here a is Nil
else
# here a is Int32
end
Анализаторы и в c++ можно использовать
Отдельно можно разобрать пример лэшем в разных системах, который упомянут, но почемуто не разобран.
Я бы решил его, обмазавшись проверками по максимому. Проверка, что результирующая строка не пустая, проверка, что файл открылся, еще к-нибудь. Как решть эоо завтипами?
Я бы решил его, обмазавшись проверками по максимому. Проверка, что результирующая строка не пустая, проверка, что файл открылся, еще к-нибудь. Как решть эоо завтипами?Требуется доказательство наличия третьего элемента в массиве, а поскольку доказательства нет, то код не пройдёт проверку.
Для ваших проверок без машины с виндой заподозрить что-то неладное не получиться.
Ну и как бы это выглядело хотябы в псевдокоде?
{-@ type PathList = {p : List | length p > 2} @-}
Так мы объявляем тип для parts. Потом, после этого явно определяем тип выраженияgetUserName :: Home -> Text
getUserName homeDir = parts !! 2
where
parts :: [Text] -> PathList
parts = splitOn "/" homeDir
После этого, можно запускать проверку типов.Ну я не увидел что хотел. Думал увидеть как это используется. Не получится ли так, что ошибка "скроется" типами и под виндой также проскочит?
К сожалению я не понял, что от меня требуется. Гарантировать безопасность cnt>=64? Если сильно упороться, то в oop языках можно создать целый класс cnt, который принимает по ссылке вектор и имеет метод с выразительным именем, который проверяет, что не превысили размер вектора
А извиняюсь, с мобильника не удобно. Но проблем все равно не вижу. Проверки, тесты, что-то можно повыносить в классы
А можно через завтипы что-нибудь сделать с типом DateTime? Ну например чтобы компилятор нам как-то мешал искать интервал между датой в московском часовом поясе и другой датой по Гринвичу.
Не, речь не о присутствии/отсутствии таймзоны.
Вопрос в том, чтобы не путать между собой даты в разных таймзонах.
Я правильно понимаю, что выражение
parseWithTZ : String -> Either ParseError (tz ** DateTime tz)
надо читать как
сигнатура функции, которая
- называется parseWithTZ
- принимает String
- возвращает Either, который содержит сообщение об ошибке парсинга или кортеж значений
кортеж содержит дату и тип
?
в условной Jawa подобная функция бы выкидывала условный FormatException и возвращала бы условный DateTimeWithTimeZone
Как тогда получается написать
case decEq tz1 tz2 of
если tz1 потенциально может вообще не быть?
Кажется тут надо более развесистый pattern-matching — предусмотреть ситуации когда не получилось преобразовать первую, вторую или обе сразу.
А можно через завтипы что-нибудь сделать с типом DateTime?
Разве для этого нужны зависимые типы? Если у нас DateTime
параметризируется тайм зоной, то этого достаточно?
Ну расскажу я, как сделать формально верифицированное красно-чёрное дерево или сортировку, неужто это будет полезно в контексте этой дискуссии?
Это будет полезно в контексте хабра. Лично я очень хотел бы видеть подобные статьи на данном сайте и был бы благодарен за такую статью.
И что новое вы создаёте ножом и вилкой?
Пусть будет не нож. Пусть будет некое сложное производственное оборудование, требующее соблюдений определенных норм ТБ и способное причинить травму, если эти нормы игнорировать.
Так устроит?
Более конкретно — есть пациент, есть хирург. У хирурга в руках скальпель. При неосторожном движении пациент может скончаться прямо на столе. Если все сделать правильно — пациент будет жив и здоров.
Где же эти разработчики с высокой квалификацией, над чем они работают?
Не знаю. Видимо, это те, кто создавали такие вещи как AS/400 (написана на плюсах), AIX, QNX (согласитесь — это достаточно серьезные коммерческие продукты).
Точно не над openssl, точно не над ядром линукса
Это бесплатные продукты. Они создаются в свободное от зарабатывания денег время энтузиастами. Никакой серьезной поддержки за ними не стоит. И никакой материальной ответственности разработчики не несут если у пользователя что-то пойдет не так.
Ну бывает, ну шутя
Сбилась с верного путя
Так ведь я — дитя природы,
Пусть дурное, но дитя
(с) Л.Филатов
Не люблю такую аргументацию, но позволю себе. Так вот, примерно за 17 лет писания кода на плюсах, из которых лет 15 я получал за это деньги, из которых лет 10 — фуллтайм, я убедился, что на плюсах (и на С) писать безопасный и надёжный код в программах сложнее хелловорлда за адекватное время невозможно.
Странно, но у меня почему-то получалось и получается.
На С начал писать в 88-м (Turbo C 1.0). С 91-го — фуллтайм за деньги. Когда перешел на плюсы не вспомню уже, помню что это был (что удалось достать) Turbo C++ 2.0
С 17-го C/C++ скорее вспомогательный язык т.к. основной — RPG (на платформе IBM i). Но часть вещей, требующих более тесного взаимодействия с системой, пишутся на С/С++.
Просто нужно понимать где и чем хорош С, и где он плох.
Ну, когда я год назад решал сваливать нафиг из плюсов, то тоже думал, что куда же мне теперь применить все мои познания и опыт в обмазывании шаблонами?
Ну если для Вас в С++ главное шаблоны…
Я вот достаточно редко использую все то, что наворотили в современном С++ И считаю это скорее злом чем добром поскольку оно скрывает в себе очень много кода и, главное, работы с объектами — разработчик перестает контролировать объекты, которые выделяются без его ведома и живут свое жизнью (а дальше начинаются непонятные утечки памяти, страдания по сборщику мусора и т.п.). Поскольку прелесть С в его прямой, фактически, работе с памятью и достаточно свободном обращении с типами. Для, для неопытного разработчика это однозначно зло.
Дай ребенку скальпель порежется. Дай скальпель хирургу — удалит опухоль.
это я тоже дурак и использую технику для дураков?
Ну учитывая недавний случай, когда двое граждан как-то умудрились обмануть так называемый «автопилот» теслы и заставить его ехать, когда на сиденье водителя вообще никого не было… поневоле задумаешься на тему того, на какую аудиторию это все рассчитано. Перефразируя известное высказывание Мольтке про приказы, «любое устройство, которое может быть использовано неправильно, обязательно будет использовано неправильно».
blind spot monitor, brake assist, радар с автообнаружением препятствий
Это всё примерно -Wall
. А автопилот автомобильный — то вообще рандом, никакими доказательствами и детерминизмом там не пахнет.
Видимо, это те, кто создавали такие вещи как AS/400 (написана на плюсах), AIX, QNX (согласитесь — это достаточно серьезные коммерческие продукты).
Сколько там уязвимостей в ms-dos? Всем срочно переходить на ассемблер.
Не люблю такую аргументацию, но позволю себе. Так вот, примерно за 17 лет писания кода на плюсах, из которых лет 15 я получал за это деньги, из которых лет 10 — фуллтайм...У вас из года в год одна и та же ошибка в логике. Вы критикуете с++ основываясь на опыте с++98/03, однако совершенно не откалибровавшись на современном с++ продвигаете раст, вышедший позже с++14
Да, новые стандарты сильно упрощают некоторые вещи и позволяют писать более выразительный и даже безопасный кодо, вы наконец-то согласились с моей аргументацией… Сколько там лет прошло?
Нет, новые стандарты не способны сделать из C++ идрис или хотя бы раст.а этого я и не утверждал. Мой поинт немного в другом — предположим раньше я бы допустил 10 ошибок памяти и 3 логических, то теперь это будет 1 ошибка памяти и 3 логических, и теперь логические беспокоят меня куда больше.
Хотя, учитывая, что я достаточно часто видел баги, связанные с использованием объекта, из которого мувнули (std::string с SSO передаёт привет), или захват в лямбду ссылки на локальную переменную, или ещё какую подобную ерунду — на самом деле не факт, что это число не вырослоэти ошибки обычно локальные и не очень тяжело отслеживаются. По краней мере найти захват по ссылке в лямбде куда проще, чем прыгать по проекту перепроверяя сырые указатели на null, а также лайфтаймы, владение и const-correctness объектов за ними.
сторонники C предлагают некий «язык для даунов», который сам аллоцирует регистры, сам настроит стекфрейм
Ну так это и есть приемлемый даунический уровень. Выше идти не всегда полезно.
Раз тут срач, повторю свою типичную мысль последних лет: миру нужны только С для лоу-левела, Сишарп для энтерпрайза и Хаскель для умных. Остальное запретить, плюсоводов ввиду большого к ним уважения — наградить именными часами.
Автор предлагает запретить использование ножей и вилок потому что ими можно порезаться/уколоться?
Нет, автор предлагает заменить опасную бритву безопасной.
Боюсь опустить карму ещё ниже, но побуду адвокатом дьявола в среде ратующих за Раст/Свифт.
Замечу, что использую раст как замену легаси плюсовому коду, так что устриц пробовал/пробую регулярно.
/Wall и ему подобные помогут благородному дону в борьбе с подобным.
В прикладном коде — отличный рецепт борььбы с подавляющим большинством ошибок уровня «мусор в optional». Практически, что проскочит компилятор взрослые дяди ловят ассертами на дебажных билдах.
Ну, а то, что уже и после юнит/автоматизированого/ручного тестирования (вы же пишете тесты, правда?) таки всплывет — так и Rust со Swift небезгрешны, плюс далеко не везде получится даже собрать нормально бинари плюсовые с частями на расте(да-да, легаси так быстро не умирает и кое-где даже шлангом собрать не очень-то и просто), а переписать все сразу — денег нет.
Rust запрещает присвоение Option<T>
в T
, ровно так, как это происходит в C++. Разница в том, что Option::unwrap
— паникует в случае None
, а его аналог из C++ — разыменование — нет.
В с++ есть optional::value() которая бросает исключение по аналогии с unwrap(). Главный плюс раста, что есть синтаксический сахар вида if let/match/?/.map() такого типа фичи и проверки от компилятора есть в том же тайпскрипте.
В данной статье речь все же не про удобство. Про удобство знаю, сам пытался использовать Option-like подход в языке, где он не "родной", было неудобно.
Наверное, можно статическим и анализатором находить и запрещать operator* для option
Непонятно только, почему operator*
не делает того же самого. Кидал бы исключение. Если программист перед вызовом operator*
корректно проверит значение сам, то оптимизатор вырежет лишнюю проверку, и потерь производительности не будет.
Видимо исторически так сложилось начиная с Си и заканчивая всей стд, что когда делаешь *x
или x->
то это самая базовая операция без всяких проверок.
Никакого "исторически сложилось" здесь нет. Это осознанное решение.
Нужны проверки (медленнее и безопаснее) — пользуйтесь std::optional::value или std::optional::value_or.
Не нужны проверки (быстрее и опаснее) — пользуйтесь std::optional::operator->, std::optional::operator*.
Интерфейс не ограничивает пользователя в возможностях.
Почему не наоборот? Value — без проверки оператор звёздочка — с ?
Наоборот — ломать логику языка.
это я и подразумевал под "исторически сложилось".
Кароче не важно, это разговор ни о чём :)
int *p = get_data();
if ( p )
( *p ) = 10;
и тут следующий код обсолютно повторяет эту логику но с другим типом данныхstd::optional< int > opt = get_data();
if ( opt )
( *opt ) = 10;
более того, это может быть обёрнуто в шаблонtemplate< typename _T >
int foo() {
_T p = get_data();
if ( p ) {
( *p ) = 10;
...
и, опять же, поведение кода будет одинаково и для указателя на int и для std::optionalв предлогаемом же вами варианте с «наоборот» поведение будет различным, а для его сохранения придётся писать специализацию шаблона, который будет вызывать ::value()
А программисту каждый раз нужно будет помнить где какой оператор вызывать, звёздочку или value(), при том, что value() сущестует только для std::optional что приведёт к куда бОльшим ошибкам по итогу.
Затем, чтобы операцией с проверкой было удобнее пользоваться, а перед использованием небезопасной программист думал, а точно ли эта операция безопасна.
Я вообще за несколько лет так и не разобрался, как пользоваться string_view. Вот принимаешь ты в параметре функции string_view, тащишь его через 10 своих методов, а в конце какой-нибудь библиотечный метод, который принимает string, и все, нужно конвертировать в string, тратить аллокацию памяти. Вот как это использовать?
PVS Studio вам в помощь )
Покажите, пожалуйста, как мне заставить PVS-studio ругаться на повисшие std::string_view
тут
Это вопрос к Andrey2008
Интересно, что-то сильно мешает разработчикам плюсовых стандартов/компиляторов помечать какие-то устаревшие и или небезопасные возможности как deprecated и просто не давать их использовать без явного разрешения (для совместимости с легаси). Внедрить в новый стандарт С++ ownership как в расте и const по умолчанию, при сохранении всего остального как было, разве плохо. А то раст местами конечно хорош, а остальное всё сломали "до основанья а затем", приходится вообще по-другому писать.
https://isocpp.github.io/CppCoreGuidelines/
The rules are designed to be supported by an analysis tool. Violations of rules will be flagged with references (or links) to the relevant rule. We do not expect you to memorize all the rules before trying to write code.
The rules are meant for gradual introduction into a code base. We plan to build tools for that and hope others will too.
что-то мне подсказывает что использование ссылки на shared_ptr не то чтобы тот кейс, для которого того создавался и вроде как явно иллюстрирует неправильную "готовку"
во-первых, в unique_ptr нет рефкаунтинга. Во-вторых, его можно передать по ссылке если хочется передать объект в функцию не передавая владение на него. В-третьих, shared_ptr придуман чтобы упростить управление лайфтаймом объектов, а не чтобы на ровном месте замедлить код дерганием атомиков
В таких случаях надо shared_ptr захватывать через std::move, кстати. Тогда и безопасно будет, и накладных расходов на рефкаунтер нет.
Это если функция принимает владение shared_ptr
. А если по факту одалживает, то будет атомарный инкремент + атомарный декремент на пустом месте.
захватывать через std::move, кстати. Тогда и безопасно будет, и накладных расходов на рефкаунтер нетописано поведение unique_ptr
В таких случаях надо shared_ptr захватывать через std::move, кстати. Тогда и безопасно будет, и накладных расходов на рефкаунтер нет.вот у вас лежит в объекте shared_ptr, и его нельзя инвалидировать, он там нужен. Надо передать его в функцию, принимающую rvalue. В итоге вам придется сделать копию, и дальше мувнуть её в метод, который эту копию потом уничтожит. Вот вам и накладные расходы на рефкаунтинг.
А, ещё иногда функции нужно начинать разделять владение не всегда, и тогда передавать всегда по значению может быть пессимизацией, но это уже более редкий кейс (но тоже возможный, да).
Что-то я с трудом представляю себе подобный случай. Можете привести пример?
Примеры, конечно такие себе. Можно и пример кода с разыменовыванием нулевого указателя привести. Тот, кто пишет на С++ так точно писать не может. Да и вообще любой мало-мальски новый компилятор тут ворнинг даст. А если еще статический анализатор подтянуть.
будут например добавляться убогие генерики вместо полноценных шаблонов
Ну у C++ есть ряд преимуществ, например наличие кучи готового кода на C++, почти бесшовная интеграция с C, наличие разных компиляторов (что также является недостатком). Но шаблоны, серьезно?
Не знаю какие языки вы имели ввиду по "заменой C++", но например в Rust можно писать плагины к компилятору (известные как "Procedural Macros") для того что не влезает в "generic" и обычные "macro_rules!". И тут C++ явно проигрывает, все-таки программировать на обычном языке программирования и на функциональном языке (а-ля "шаблонах c++") в котором и отладочную печать хрен реализуешь это совершенно разные вещи.
А часть того что делают "Procedural Macros" вообще реализовать нельзя в рамках текущего стандарта, я имею reflection, который вроде в c++23 будет или позже.
Макросы ортогональны шаблонам а те в свою очередь дженерикам. То что можно сделать на макросах в расте точно так же достигается каким-то сторонним кодогеном на основе libclang. Или можно вспомнить Qt moc которому уже очень много лет. На расте оно сделано все более удобно с точки зрения туллинга, для небольших и средних проектов это плюс. То что можно сделать на шаблонах в общем случае нельзя сделать ни на макросах ни на дженериках.
о что можно сделать на шаблонах в общем случае нельзя сделать ни на макросах ни на дженериках.
А можно пример, того что нельзя сделать с помощью плагина к компилятору, но можно сделать на шаблонах?
А можно пример, того что нельзя сделать с помощью плагина к компилятору, но можно сделать на шаблонах?отвязать язык от компилятора?
В шаблонах есть оверлоадинг в зависимости от типа аргумента и других его свойств. Сам шаблон может быть передан аргументом тоже.
С помощью плагина компилятора наверное вообще можно сделать другой язык. Только это будет уже не раст. И с обычным растом из других крэйтов интегрироваться тоже будет хреново.
Шаблоны это и есть другой язык с другими парадигмами программирования и другими навыками для его освоения.
Не вижу, в чем будет проблема в интеграции "обычного раста" и плагинов компилятора. Плагин просто генерирует код, совершенно такой же, какой вы бы сами написали. Никакой магии он не делает
Проблемы с версиями испокон веков решаются менеджерами зависимостей. Не быть его не может — это такая же часть проекта, как все остальные зависимости проекта. Вас же не удивляет, что если у компилятора C++ проекта не заданы пути к заголовкам используемых библиотек, то проект не собирается? Почему в случае с плагином к компилятору должно быть по другому? Аналогично с версиями.
И потом, еще можно поспорить, что там лучше поддерживается. Насколько у шаблонов C++ все хорошо с совместимостью? А то поддерживается там может только на бумаге. Или только в новейшей версии компилятора. Сколько тут людей плакалось, что на проекте древний как гавно мамонта GCC и обновить его нельзя?
Проблемы с версиями испокон веков решаются менеджерами зависимостей. Не быть его не можетСурприз-сурприз.
Насколько у шаблонов C++ все хорошо с совместимостью?Для этого есть стандарт языка и щаблоны часть его. А стандарт для плагина можно посмотреть?
На днях как раз была статья из серии «оно не логично, но оно так на всех компиляторах, потому что поведение описано в стандарте», что-то там про знаковые и беззнаковые вычисления.
Сколько тут людей плакалось, что на проекте древний как гавно мамонта GCC и обновить его нельзя?Люди хотят новый фичей, которых в старом компиляторе нет, но при этом они могут собрать проект любой версией новее. Если писать на основе стандарта С++98/03 то можно будет собрать вообще любым компилятором.
Сурприз-сурприз.
Сомневаетесь, что менеджер зависимостей скачает зависимость или что?
Для этого есть стандарт языка и щаблоны часть его.
И как долго используемый в проекте компилятор придерживается современного же этой версии компилятора стандарта? А то я помню, что на это было много жалоб.
А стандарт для плагина можно посмотреть?
Можно. Открываете грамматику языка и вперед.
Люди хотят новый фичей, которых в старом компиляторе нет, но при этом они могут собрать проект любой версией новее.
Почему вы решили, что хотят именно новых фич? Может у них там встраиваемая система какая-то, компилятор для которой какой дали, тем и пользуйся, а другие просто не умеют под нее компилировать.
Сомневаетесь, что менеджер зависимостей скачает зависимость или что?Что он используется в случае с С++, и опять же — который из множества, даже CMake умеет в FetchContent из git, а это ни разу не пакетный менеджер :)?
И как долго используемый в проекте компилятор придерживается современного же этой версии компилятора стандарта?Не понял. В целом — всегда. Не меняешь компилятор — не меняется ни чего в языке и стандарте, который он поддерживает. Нужен С++20, то да, GCC 4.8.5 не подходит, нужно обновить до 11-й версии, ну а новой версии можно указать "-std=c++11" и GCC 11 «магическим» образом превращается в тыкву, в смысле работает только с поддержкой стандарта С++11 и ни чего новее не понимает.
Можно. Открываете грамматику языка и вперед.Ну вот то есть, если я в системе сборки на основе CMake вызываю, скажем, configure_file(foo.h.in foo.h ONLY) и мне на основе шаблонного файла foo.h.in будет сгенерирован foo.h это имеет какое-то отношение к синтаксису языка? И мне не нужно изучать что такое #cmakedefine, внезапно, не описанный в стандарте языка С++?
Может у них там встраиваемая система какая-то, компилятор для которой какой дали, тем и пользуйся, а другие просто не умеют под нее компилировать.Именно, потому что почти все компиляторы для встройки поддерживают С++98/03 и редко когда умеют в С++11/14 не говоря про С++17
Проблемы могут быть только если в компиляторе покопались шаловливые ручки «тех кто дал» для поддержки специфичных функций.
Что он используется в случае с С++
Мы же обсуждали, каким образом плагина компилятора вдруг не окажется в проекте, где он нужен. В каком из компиляторов C++ есть плагины?
Rust проекты используют менеджер зависимостей и там такой проблемы не может быть.
даже CMake умеет в FetchContent из git
Ну вот, а вы говорите, откуда возьмется плагин правильной версии. Оттуда же, откуда библиотека правильной версии.
Не понял. В целом — всегда.
Я имею ввиду не "в версии A.B.C
перестаем поддерживать стандарт версии X
", а "Все ли из того, что написано в стандарте версии X
, реализовано в компиляторе версии A.B.C
, или на словах у нас стандарт X
, а не деле стандарт A
(где A
меньше X
)?"
Ну вот то есть, если я в системе сборки на основе CMake вызываю, скажем, configure_file(foo.h.in foo.h ONLY) и мне на основе шаблонного файла foo.h.in будет сгенерирован foo.h это имеет какое-то отношение к синтаксису языка? И мне не нужно изучать что такое #cmakedefine, внезапно, не описанный в стандарте языка С++?
Для того, чтобы понять, что написано в сгенерированном foo.h
стандарта достаточно. А уж откуда этот файл появляется — хоть заменой каких-то #cmakedefine
в foo.h.in
, хоть генерируется еще из какого-то другого места — значения не имеет. У вас есть "плагин" CMake, который выдает код, который интегрируется с любым другим кодом на C++ в той же мере, в какой код на Rust, выдаваемый плагинами к компилятору Rust-а, интегрируется с другим кодом на Rust.
Проблемы могут быть только если в компиляторе покопались шаловливые ручки «тех кто дал» для поддержки специфичных функций.
Вот. Но вероятно генерация кода под какой-нибудь специфичный чип невозможна без копания шаловливыми ручками. Что предлагает C++, если мы все же хотим пользоваться современными шаблонами? Не пользоваться ими.
Что бы предложил компилятор, к которому мы можем писать плагины? Рядом с основным проектом написать плагин, выдающий код, который может быть скомпилирован древней версией компилятора. Разумеется, предполагается, что все версии компилятора имеют интерфейс для плагинов, что в случае Rust-а по большей части так и есть.
Мы же обсуждали, каким образом плагина компилятора вдруг не окажется в проекте, где он нужен.Это не часть языка. Ну и если плагины скачиваются из git, то даже указывая версию 1.10.5 не факт, что получишь именно её, так как rebase и прочие трюки ещё ни кто не отменял.
Все ли из того, что написано в стандарте версии X, реализовано в компиляторе версии A.B.C, или на словах у нас стандарт X, а не деле стандарт A (где A меньше X)?gcc.gnu.org/projects/cxx-status.html
clang.llvm.org/cxx_status.html
docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=msvc-160
Но вероятно генерация кода под какой-нибудь специфичный чип невозможна без копания шаловливыми ручками. Что предлагает C++, если мы все же хотим пользоваться современными шаблонами? Не пользоваться ими.Боюсь спросить как в таком случае будут обстоять дела с Rust? Не использовать его вообще?
В случае С++, как я и сказал, вариант стандарта С++03 будет работать всегда, не важно под какой чип.
Это не часть языка.
Интерфейс к вызову плагинов — часть языка. Плагины естественно не часть языка, иначе это были бы не плагины.
Ну и если плагины скачиваются из git, то даже указывая версию 1.10.5 не факт, что получишь именно её, так как rebase и прочие трюки ещё ни кто не отменял.
Про git это вы упоминали с контексте CMake. Так-то зависимости конечно нужно качать из репозитория, где они не меняются. В любом случае, это не меняет принципиально ничего — плагины к компилятору есть обычные библиотеки, и тем же обычным способом оказывается в зависимостях проекта, как и всякие другие библиотеки.
gcc.gnu.org/projects/cxx-status.html
GCC has experimental support for the latest revision of the C++ standard, which was published in 2020.
clang.llvm.org/cxx_status.html
C++20 -std=c++20 Partial
https://docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=msvc-160
Тут сложно сказать, в каком состоянии поддержка последнего стандарта, информации много и она размазана, но я вижу с десяток No в разделе C++20.
И это самые новые релизы компилятора, стандарт год назад, как вышел, а обсуждался так вообще не меньше 3-х лет. А по факту выходит, что не хочешь внезапного возникновения проблем — жди еще года 3, пока поддержку наконец сделают полностью.
Боюсь спросить как в таком случае будут обстоять дела с Rust? Не использовать его вообще?
В случае С++, как я и сказал, вариант стандарта С++03 будет работать всегда, не важно под какой чип.
По вашей логике выходит, что любой компилятор C++ компилирует полю любой чип в режиме С++03; clang в том числе; clang генерирует код с помощью LLVM; Rust тоже генерирует код с помощью LLVM; значит с помощью Rust тоже можно написать код под этот чип.
Тут сложно сказать, в каком состоянии поддержка последнего стандартаЭммм… ниже таблицы со всеми изменениями, со ссылками на каждый документ-изменение.
И это самые новые релизы компилятора, стандарт год назад, как вышел, а обсуждался так вообще не меньше 3-х лет.Это вы так читаете или пытаетесь натянуть сову на глобус, подогнать реальность под своё мнение?
Финальная спецификация С++20 вышла в декабре 2020, ну то есть меньше полугода назад.
GCC
C++20 features are available since GCC 8
Clang
Default member initializers for bit-fields P0683R1 Clang 6
На то, что в Clang уже заявлена поддержка некоторых возможностей С++23 вы просто закрыли глаза :)
MSVC
P0704R1 Fixing const lvalue ref-qualified pointers to members VS 2015
В основном поддержки нет у тех вещей, которые и не были стандартихованы до декабря, те же модули, например.
clang в том числевы, видимо, не пишите под встройку, там правит бал GCC. Мне вообще интересно наблюдать, как вы обсуждаете область, где знания близки к теоритическим :)
Эммм… ниже таблицы со всеми изменениями, со ссылками на каждый документ-изменение.
Ага. Очень полезно, если я хочу узнать текущее состояние дел. Высчитывать по диффам поддерживаемые возможности на конкретную дату. Да ну нафиг, я не git.
C++20 features are available since GCC 8
Вот прям все-все? И багов нигде нет? Да и еще через полгода после финального релиза стандарта (вы там определитесь уже, или полгода стандарт как вышел, что ажно глобус с совой портятся, или 3 года как уже все готово (https://gcc.gnu.org/releases.html говорит, что 8.1 вышел 2 мая 2018))? Не верю.
На то, что в Clang уже заявлена поддержка некоторых возможностей С++23 вы просто закрыли глаза :)
Вот я выделил ключевое слово во всех этих восторженных отзывах. Стандарт как бы на то и стандарт, чтобы быть реализованным в полном объеме, а не "некоторыми возможностями". Полагаю, цитата
Default member initializers for bit-fields P0683R1 Clang 6
как раз и говорит об одной из "некоторых возможностей" стандарта C++20, и должна показать, что типа стандарт давно уже реализован. Может там даже что-то из C++32 уже есть, мм :)?
В основном поддержки нет у тех вещей, которые и не были стандартихованы до декабря, те же модули, например.
А… ну да… чуть-чуть беременна.
вы, видимо, не пишите под встройку, там правит бал GCC. Мне вообще интересно наблюдать, как вы обсуждаете область, где знания близки к теоритическим :)
Неа, не пишу. Просто интерпретирую мысли, которые тут вокруг роятся. Если они неправильные… что ж… как говорится, gargabe in — garbage out :).
Высчитывать по диффам поддерживаемые возможности на конкретную дату. Да ну нафиг, я не git.А ви, таки, хотите всё и сразу или конкретную функциональность?
вы там определитесь уже, или полгода стандарт как вышел, что ажно глобус с совой портятся, или 3 года как уже все готовоВи, таки, прочитайте как формируется стандарт и почему одни части документа были готовы ещё три года назад, а некоторые окончательно смогли оформить только через пару лет. И почему по документам, которые уже не изменятся, начинают реализовывать функциональность компилятора. Много полезного узнаете. Кстати, это покажет, кем вы себя демонстрируете перед обществом.
Просто интерпретирую мысли, которые тут вокруг роятся.Можно тогда я интерпретирую мысль, что у Rust крайне токсичное сообщество и ему не место в приличном обществе, на основе, таки, вашего поведения, или как ви сказали
gargabe in — garbage out
А ви, таки, хотите всё и сразу или конкретную функциональность?
Ну то есть начали с того, "для шаблонов есть стандарт, см. туда", смотрим "туда", а там запросто может быть "ой, вы знаете, это еще не поддерживается и когда будет, не знаем. Нет, что вы, стандарт-то мы поддерживаем".
Много полезного узнаете.
Например, каким образом GCC, вышедший в 2018, поддерживает стандарт, вышедший в 2020 (заметьте, обе даты по вашей информации, а не моей)? Это действительно было бы интересно. Напомню, что этой фразой вы отвечали на вопрос
Все ли из того, что написано в стандарте версииX
, реализовано в компиляторе версииA.B.C
, или на словах у нас стандартX
, а не деле стандартA
(гдеA
меньшеX
)?
почему одни части документа были готовы ещё три года назад, а некоторые окончательно смогли оформить только через пару лет
Какой отношение история документа имеет к бинарному признаку поддерживаем/не поддерживаем стандарт?
Можно тогда я интерпретирую мысль,
Можно конечно, ведь, как уже неоднократно упоминали
gargabe in — garbage out
так что не так уж важно, что вы там наинтерпретируете :)
Ну то есть начали с того...У стандарта периодичность выхода — раз в три года. А у компилятора? Это как раз было к тому, что поддержка вводится постепенно, параллельно с формированием стандарта. Зафиксированная и неизменная функциональность реализуется сразу после публикации, раньше чем выходит финальное издание стандарта, таким образом лаг между публикацией и поддержкой составляет несколько месяцев, а не три года. Я же говорю — сперва почитайте, а потом обсуждайте, что бы не выглядеть глупо.
Например, каким образом GCC, вышедший в 2018, поддерживает стандарт, вышедший в 2020...Ну то есть то, что спецификация по функциональность может быть зафиксирована до окончательного издания стандарта три года назад, быть реализована ещё в 2018-м и, и, благодаря стандарту, работать одинаково и в GCC и в clang и в MSVS для программиста на Rust непостижимое явление, или только ви такой особенный?
Какой отношение история документа имеет к бинарному признаку поддерживаем/не поддерживаем стандарт?Родителями становятся за 9 месяцев до непосредственно рождения ребёнка, вас же не удивляет, что одежду, пелёнки и прочее покупают заранее, а не после родов… или удивляет?
Зафиксированная и неизменная функциональность реализуется сразу после публикации, раньше чем выходит финальное издание стандарта, таким образом лаг между публикацией и поддержкой составляет несколько месяцев, а не три годану, поддержка стандарта 2020 года в компиляторах середины 2021-ого года оставляет желать лучшего. Например, модули реализованы только в gcc, и то частично, а изменения стдлибы реализованы только в msvc.
ну, поддержка стандарта 2020 года в компиляторах середины 2021-ого годаДата публикации стандарта и, в частности, финальной версии модулей — декабрь 2020 не смущает? :)
…
Например, модули реализованы только в gcc
4 месяца на реализацию принципиально другого подхода к сборке, вы серьёзно? :)
Дата публикации стандарта и, в частности, финальной версии модулей — декабрь 2020 не смущает? :)вы забываете что стандарт публикуется с весьма существенным лагом после финализации. Которая, емнип, была еще весной 2020.
Ну так и называйте "поддержка стандарта pre-с++XX-1", "поддержка стандарта pre-с++XX-2", ..., "поддержка стандарта pre-с++XX-Y" почему она вдруг стала "поддержка стандарта с++XX"?
может быть… благодаря стандарту, работать одинаково и в GCC и в clang и в MSVS
Или не работать. Каждый из компиляторов может реализовать разные части стандарта. Да, все реализуют то, что уже не меняется, но пользоваться этим невозможно, если вы только не прибиты гвоздями к одному компилятору, чего, кажется, все стремятся избегать.
Родителями становятся за 9 месяцев до непосредственно рождения ребёнка
Да ладно? А если роды преждевременные, то родители уже до зачатия? А материнский капитал они, как родители, потребовать могут? А если аборт? А то я всегда считал, что это "будущие родители". Само слово "родители", кстати, имеет весьма говорящий корень.
вас же не удивляет, что одежду, пелёнки и прочее покупают заранее, а не после родов
Ну вот и стандарт пишут до реализации в компиляторе, а не после… или нет?
почему она вдруг стала «поддержка стандарта с++XX»?Наверное потому, что на тот момент стандарт включал только ту функциональность, нет? Может хватит позориться? :)
Так у нас стандарт имеет конкретное имя с точным набором функций или в него что левая пятка комитета захочет суют каждый день?
Если есть некий "тот момент", то и называется он "стандарт C++XX-тот-момент", а не "стандарт C++XX"
Само слово «родители», кстати, имеет весьма говорящий корень.оффтопик, но корень — род
Ряд поколений, происходящих от одного предка, а также вообще поколение.
вы перепутали причину со следствием, что не удивляет.
Ну то есть начали с того, «для шаблонов есть стандарт, см. туда», смотрим «туда», а там запросто может быть «ой, вы знаете, это еще не поддерживается и когда будет, не знаем. Нет, что вы, стандарт-то мы поддерживаем».есть такая штука как feature test macros — как раз для разруливания частичной поддержки стандарта.
Сколько тут людей плакалось, что на проекте древний как гавно мамонта GCC и обновить его нельзя?обычно когда люди плачут о древнем как говно мамонта GCC, он старше, чем первый стабильный релиз rust, но кажется вас это не сильно волнует…
А что должно волновать? Старше — значит вышел раньше, чем даже первый стабильный релиз rust, почему бы и не плакать?
А что должно волновать? Старше — значит вышел раньше, чем даже первый стабильный релиз rust, почему бы и не плакать?в момент, когда кто-то выбирал условный gcc 4.8 в качестве компилятора, он при всем желании не смог бы использовать стабильный rust. Я даже удивлен, что это может требовать пояснений
Конечно не смог бы. И если он теперь не может обновится с этой версии компилятора на более новую, ему разве не нужно посочувствовать? Ведь его компилятор древнее даже первой версии rust-овского, это ж какая древность. Непонятно, какие именно еще пояснения к этому вы намерены давать.
Рядом с основным проектом написать плагин, выдающий код, который может быть скомпилирован древней версией компилятора.
Это какие-то невероятные фантазии. Удачи написать "плагин" который код с async (одно из свежих значительных изменений) переделывает в код без async, и при этом ещё каким то чудесным образом можно использовать свежий рантайм tokio.
Нет, ну если задача так стоит — переделать новый код на Rust-е в старый код на Rust-е, то задача может быть велика, хотя и решаема. А вот на плюсах сделать шаблон, который Variadic шаблон (одно из значительных изменений в плюсах) сделает не Variadic, и чтобы он работал там, где поддержки Variadic-ов нет, принципиально невозможно. Все упадет еще на стадии лексического анализа.
Если задача скромнее — имея свой DSL генерировать по нему код, то вместо генерации кода с теми же async
ами генерировать код без них — очень даже подъемная.
А вот на плюсах сделать шаблон, который Variadic шаблон (одно из значительных изменений в плюсах) сделает не Variadic, и чтобы он работал там, где поддержки Variadic-ов нет, принципиально невозможноУдивительно, и как же оно работало до появления С++11… да, было очень некрасиво с перегрузкой (или кучей макросов) на N+1 вариантов, но работало, значит не невозможно :)
Принципиально не реализуемая на С++03 фишка — move-семантика, остальное решается так или иначе.
Ну да, работало. Только свой код вам переписать придется, так как всякие Args...
старый компилятор, увы, не понимает. И только средствами языка это не решить. У Rust-а компилятор не интересуется, что там передается на вход процедурному макросу (и если специально выбрать тип — что на вход декларативному макросу), можно и свой синтаксис изобрести, главное, чтобы скобки были сбалансированы. Т.е., используя только сам язык, подобная проблема решаема.
Ну это же просто бестолковое буквоедство. То что там формально макрос это часть спецификации с задачей такого уровня ну не помогает никак. Если надо компилировать новый С++ код старым компилятором то просто делаете так:
./trollface_cxx17_to_cxx03_сonverter code.cpp
g++ code.cpp
К тому же даже формально как вы сконвертируете код из подключаемого крейта через макрос в вашем коде? Такого в спеке раста нету — так что облом.
Но повторюсь, ваш trollface_macro_converter!
это именно что троллинг.
upd: trollface_cxx17_to_cxx03_сonverter пишете на С++ так что используете только язык чтобы решить задачу.
Если надо компилировать новый С++ код старым компилятором то просто делаете так:
Так в любом языке можно сделать, так что этот аргумент можно не учитывать, он всем одинаковые очки приносит.
К тому же даже формально как вы сконвертируете код из подключаемого крейта
А зачем нам конвертировать чужой крейт? Это его автор должен делать. А мы пользуемся той версией крейта, которая поддерживается компилятором.
Это все равно, что попросить вас на C++ трансформировать шаблоны чужой библиотеки с помощью шаблонов вашего проекта.
принципиально невозможно
Ну да, работало.Хорошо, пациент на пути к выздоровлению.
У Rust-а компилятор не интересуется, что там передается на вход процедурному макросуВ С++ тоже, это же макрос.
Тут уже приводили пример подобного плагина — Qt moc. Его даже умудрились переписать на С++14 если правильно помню, то есть решить все его расширения синтаксиса через шаблоны и макросы.
То, что Rust позволяет это сделать проще — круто, всё же сколько лет между созданием языков прошло? Но а дальше то что?
Может у них там встраиваемая система какая-то, компилятор для которой какой дали, тем и пользуйся, а другие просто не умеют под нее компилировать.
Если ты можешь скомпилировать под систему с помощью rustc то ты можешь скомпилировать с помощью последних clang++ и gcc с поддержкой новых фич. Если ты не можешь скомпилировать с помощью rustc то есть шанс что clang++ все еще работает, и есть больший шанс что и gcc работает. Если есть только какой-то вендорный компилятор то раста там нету и в помине, так что и вспоминать не приходится.
Иногда компиляторы при обновлении что то ломают, типа код работал быстро а стал медленно. Такое встречается везде и раст не исключение потому что для оптимизаций машинного кода там используется LLVM (кстати не давно был в расте баг что -march=native
сюрприз-сюрприз замедлял код, и это была именно проблема растового фронтенда).
Какие еще могут быть причины что люди сидят на старых компиляторах, которые мог бы решить раст?
Портирование кода с gcc 6 на стандартный C++17 было на моём последнем месте работы отдельным проектом примерно на месяц, хотя там всего-то была этак сотня kloc кода, плюс одни из топовых спецов по плюсам, которых я встречал.а что конкретно было причиной? Обычно в сотне kloc не так много мест, чтобы перевод на новый стандарт был проблемой. Если у вас только не использовался _GLIBCXX_USE_CXX11_ABI, но этот перевод должен был происходить лет 6 назад?
Плагин генерирует код в одном конкретно месте, на основе AST того кода который вы обернули в макрос. Чтобы плагин умел что-то делать подобное на С++ шаблоны (заметьте нужно это или нет я вообще не обсуждаю) он должен знать все обо всем коде. Поскольку он ничего не знает о коде за своими пределами он не может интегрироваться в язык, — в том смысле в котором это делают темплейты. Обычно связь макросов с другим кодом происходит посредством трейтов/дженериков. А дженерики это далеко не шаблоны.
Ну, шаблоны то в общем-то тоже ничего не знают обо всем коде, да и не могут, т.к. этого кода в момент их написания еще нет. И когда в ваш якобы всезнающий шаблон приходит что-то, на что он не рассчитан, все падает с километровыми логами ошибок. А километровые они потому, чтобы сделать какие-то простейшие действия, приходится городить кучу шаблонов на шаблонах. А потом компилятор выводит все это непотребство пользователю, потому он не знает, что там важно, а что нет. Какая же тут "интеграция в язык"?
Конечно ситуация улучшается, но со стороны это выглядит, как филигранное вытачивание костылей, чтобы не дай бог не задеть другие ажурные конструкции из костылей, на которых все и держится. И улучшение естественно возможно только в одном направлении — уменьшить всезнаемость этих шаблонов (которой на самом деле нет) и явно обозначить компилятору, где важные части, которые должны быть в сообщении об ошибке, а где нет.
Чтобы плагин умел что-то делать подобное на С++ шаблоны
Вот все говорят подобные фразы, а где примеры этого "чего-то подобного"? Пока я вижу как раз наоборот — в Rust-е есть, например, serde, реализующий фактически compile-time рефлексию, а вот плюсовые библиотеки для того же полагаются на особенности компиляторов. До поищите хотя бы статьи здесь, сколько было предложено велосипедов для сериализации, в везде нужно что-то делать руками и не в одну строчку.
Вы смешиваете в одном обсуждении конкретные проблемы каждого из подходов с обсуждением принципиального различия подходов.
Например если я вызываю my_template<X>
то внутри темплейта может быть различная логика в зависимости от того X это int
или std::string
, или еще какие то более сложные свойства а ля есть ли конструктор копирования. Если я вызываю my_macro!(X)
то макросу приходит только информация что X
это какой-то индентификатор. Но на уровне макросов про него ничего не известно. Поэтому шаблон знает про икс (который объявлен в любом другом месте кода) много чего, а макрос ничего не знает.
а где примеры этого "чего-то подобного"?
Например та же специализация в расте используется в std либе для итераторов (чтобы оптимизировать Random-Access итераторы) не смотря на то что это найтли фича и в стейбл расте ее нету. В С++ это уже давно было и есть, начиная с С++11 наверняка. Только заметьте это реализуется не макросами а именно расширением возможностей дженериков в расте. Потому что на уровне макросов узнать есть Random-Access или нету невозможно.
serde это хороший пример удобной интеграции макросов в эко-систему (а не в язык, т.е. конечно можно сказать что интеграция в язык но в более общем смысле). По своей сути он ничем не отличается от Qt-moc или какой то генерации сериализаторов на основе protobuf спеки. То что для макросов в расте есть вполне конкретный формат аттрибутов, это тоже безусловно плюс, это лучше чем когда каждая либа лепит свой формат комментариев для этих целей.
По своей сути он ничем не отличается от Qt-mocВот только что бы Qt-moc работал с тем же MinGW, пропатченный компилятор входит в комплект поставки Qt, иначе «не заведётся», что тоже говорит о «стандартности» moc-а :)
Например если я вызываюmy_template<X>
то внутри темплейта может быть различная логика в зависимости от тогоX
этоint
илиstd::string
, или еще какие то более сложные свойства а ля есть ли конструктор копирования. Если я вызываюmy_macro!(X)
то макросу приходит только информация чтоX
это какой-то индентификатор.
Не вижу в вашем примере, где в my_template
передаются все эти "какие то более сложные свойства а ля есть ли конструктор копирования". Точно также как в в Rust-е всего лишь идентификатор.
Все эти свойства узнаются с помощью другого шаблона вроде type_traits
, который просто тупо реализован под все поддерживаемые типы, а все неподдерживаемые идут мимо.
В Rust-е же типаж — конструкция языка. В макросе ставите требование:
trait Clone : Sized {// это стандартная библиотека Rust
fn clone(self) -> Self;
}
// потроха my_macro!(X)
fn do_generation() -> TokenStream {
// Тут получаем токен, представляющий X, переданный в макрос
let X = ...;
// Макрос quote! генерирует код на Rust в нужном формате
// из уже написанного Rust кода, плюс поддерживает интерполяцию
// переменных, записанных через #variable
quote! {
// Объявляем функцию/тип которая требует определенных свойств
// от типа, с которым работает... а ля есть ли конструктор копирования
fn generated_code<T: Clone>(obj: T) {
// Делаем что нам надо...
obj.clone();
}
// Вызываем функцию с типом на входе макроса.
// Заметьте, что проверять, можно ли это сделать, не надо,
// это сделает компилятор в своем обычном цикле компиляции
generated_code<#X>();
}
}
Все. При компиляции получаем generic-функцию generated_code
, которая работает только с типами, реализующими типаж Clone
. Если передадите в макрос что-то не-клонируемое, то получите ошибку компиляции, вполне внятную.
Не обязательно эту generic-функцию генерировать — можно использовать уже существующую, написанную вне макроса.
Поэтому шаблон знает про икс (который объявлен в любом другом месте кода) много чего, а макрос ничего не знает.
Макрос знает ровно столько же, сколько шаблон, а иногда даже больше, если вы передаете в макрос не только идентификаторы, а часть синтаксического дерева Rust. Да даже просто уже можно знать, что у вас на входе Rust-строка, если вы вызываете макрос как
my_macro!("строка");
Только заметьте это реализуется не макросами а именно расширением возможностей дженериков в расте. Потому что на уровне макросов узнать есть Random-Access или нету невозможно.
Ну в принципе, невозможным не выглядит. Как-то так можно:
trait OptimizedAccess {
fn skip(&mut self, count: usize);
}
impl OptimizedAccess for RandomAccessIter {
fn skip(&mut self, count: usize) {
// просто прыгаем на count элементов вперед
}
}
impl OptimizedAccess for NonRandomAccessIter {
fn skip(&mut self, count: usize) {
// крутимся в цикле, пока не вычитаем count элементов
}
}
trait Iterator {
pub fn skip(self, n: usize) -> Skip<Self>
// Полагаю, проблема в библитечном коде из-за этой строчки
// Ее сложно добавить без слома обратной совместимости
// В оригинальном типаже ее нет
where Self: OptimizedAccess
{
OptimizedAccess::skip(&mut self, n);
...
}
}
Проблема в том, что решение потребует изменения типажа Iterator
на что не идут по каким-то причинам — вероятно, не удается это сделать совместимым образом. Или просто решили сосредоточится на более общей фиче, которая решит и эту проблему в том числе без изменений типажа. Тем не менее, сделать это можно и каких-то особых C++ знаний растовскому макросу не потребуется.
Я в курсе как работают макросы. И вы только что подтвердили все что я написал, что все решения делаются на дженериках+трейтах или шаблонах но никак не макросами.
По пунктам:
внутри макроса нельзя написать
if X.is_сlonable() {
quote! { ... }
} else {
quote! { ... }
}
внутри темплейта можно:
void f(auto x) {
if constexpr (is_copy_constructible_v<decltype(x)>) {
...
} else {
...
}
}
или же так
const s: &'static str = "cтрока";
my_macro!(s); // облом. уже не стринг литерал на входе
Т.е. по факту макрос не знает что вы передали строку.
Темплейт будет работать одинаково с обеими вариантами.
Ну в принципе, невозможным не выглядит. Как-то так можно:
В каком месте тут макросы? Опять трейты дженерики.
Если мы хотим обсуждать дженерики версуз шаблоны то это уже другой разговор.
По поводу RandomAccessIter/NonRandomAccessIter
Во первых сразу минус такого решения. Сейчас мне нужно чтобы имплементировать свой итератор просто сделать метод next()
и уже готово. В вашем решении мне ещё надо будет делать impl OptimizedAccess
и импелементировать метод skip()
. При чём для итераторов у которых нету RandomAccessIter это будет каждый раз один и тот же код. А это не только метод skip()
а ещё наверное штук пять. Может быть в таком простом примере вам удастся выкрутится с помощью того что в трейтах есть имплементация по умолчанию (такой частный случай специализации).
Во-вторых представим я делаю zip двух итераторов, если оба RA то результат должен быть тоже RA. При чём без специализации это будет вместо двух кейсов (if (оба ра) {} else {}) — четыре метода в расте. Если представить что кроме RA у итераторов есть еще парочку свойств на которые надо реагировать, то можно быстро получить комбинаторный взрыв.
Добавлено:
Вот вам пример по-проще на специализацию.
внутри темплейта можно:
Да нет, там все делает is_copy_constructible_v<decltype(x)>
, что по сути вызов метода какого-то метода у типажа IsCopyConstructable
, который должен реализовывать тип. Конкретно этот реализуется компилятором на основе формы типа для всех типов, в Rust-е аналогичным образом реализуются Send
, Sync
, Sized
и некоторые другие. Так как парадигма Rust-а отличается от парадигмы C++, то без знания того, что там в ветках if
аналог привести сложно. Если просто выбирается, как копировать тип (для чего еще проверять способ конструирования?), он может выглядеть просто как
fn f<T: Clone>(x: T) {
x.clone();
}
В общем то да, никаких макросов в таком простом примере не требуется.
Темплейт будет работать одинаково с обеими вариантами.
В C++ шаблоны строки тоже не передаются как шаблонные параметры. Насколько я помню, только интегральные типы допустимы, а строка таковым не является.
Сейчас мне нужно чтобы имплементировать свой итератор просто сделать метод next()
и уже готово.
Если захотите поддержки для RandomAccess со стороны стандартной библиотеки, каким-то образом вам все равно придется сказать, что ваш возвращаемый итератор — RA. И я уверен, это будет делаться через реализацию типажа, т.к. в этом их назначение — описывать свойства типов.
вашем решении мне ещё надо будет делатьimpl OptimizedAccess
и импелементировать методskip()
.
Покажите, как вы это сделаете принципиально иным образом.
При чём для итераторов у которых нету RandomAccessIter это будет каждый раз один и тот же код.
Как раз для решения этой проблемы и пилится специализация. Чтобы предоставить реализацию по умолчанию и возможность перекрывать эту реализацию для нужных типов. Сейчас это невозможно, т.к. реализации будут конфликтовать.
Во-вторых представим я делаю zip двух итераторов, если оба RA то результат должен быть тоже RA.
Да, думаю, что сейчас сложно сделать функцию, которая будет возвращать типы с разными свойствами на основе наличия свойств в других типах. С другой стороны, это не требуется, т.к. ZipIter
все равно не должен сам как-то продвигать итераторы своих компонентов, а должен делегировать им эту работу. И там уж не важно, как эти компоненты работают. Изучать внешним кодом признак RA на ZipIter
тоже не нужно — мы уже выяснили, что для этого нужно спрашивать реализацию OptimizedAccess
, а ее реализовать тривиально:
struct ZipIter<I1, I2> {
it1: I1,
it2: I2,
}
impl<I1, I2> OptimizedAccess for ZipIter
where I1: OptimizedAccess,
I2: OptimizedAccess,
{
fn skip(&mut self, count: usize) {
self.it1.skip(count);
self.it2.skip(count);
}
}
Да нет, там все делает is_copy_constructible_v<decltype(x)>
Так это и есть темплейт внутри темплейта, здрасте. Была тема про темплеты vs макросы, а съехали на трейты. А я в самом первом сообщении написал что это три разных вещи.
без знания того, что там в ветках if аналог привести сложно.
Я не обсуждаю нужно это или нет (уже выше так и написал один раз). Я обсуждаю что можно сделать а что нет. Пример просто взял из головы, — первый type_traits что пришел на ум. Кстати в С++ оно через концепты или sfinae делается. Компилятор лишь предоставляет наличие метода с определённой сигнатурой. Но это не важно, в макросе у вас нету информации никакой всё равно. Она есть у компилятора уже потом когда макрос раскрылся.
Покажите, как вы это сделаете принципиально иным образом.
Все правильно, так и делаю. Только не каждый раз, а только для тех итераторов у которых реально есть RA. О том собственно и речь.
Сейчас это невозможно, т.к. реализации будут конфликтовать.
А чо макросы не помогли да? Это же плагин компилятора, который может всё. Как же так случилось...
спрашивать реализацию OptimizedAccess
Это не совсем так. На основе того что мы знаем о I1 и I2 мы можем оптимизировать ZipIter::next()
. Если мы дергаем i1.next()
и i2.next()
— имеем две проверки на доход итератора до конца. Если мы знаем длину заранее то можно сделать n = min(i1.len(), i2.len())
в конструкторе ZipIter
. Тогда можно сэкономить одну проверку. Плюс еще сэкономить махинации с заворотом и разворотом Option<Item>
. Что и делают в std либе раста.
Кстати я там еще пример добавил про сравнение слайсов в предыдущем сообщении.
Изучать внешним кодом признак RA на ZipIter тоже не нужно
Кода вы делаете impl RA for ZipIter
вы как раз навешиваете признак RA на собственно ZipIter :) А потом когда вы используете ZipIter вы изучаете что там у него есть, и в зависимости от этого делаете одно или другое… Когда вы объявляете MyClass(const MyClass& other) то навешиваете признак, потом в шаблоне изучаете, как раз тут принцип похож.
Так это и есть темплейт внутри темплейта, здрасте. Была тема про темплеты vs макросы, а съехали на трейты. А я в самом первом сообщении написал что это три разных вещи.
Ну так что ж делать, если вы предлагаете задачи из области, которые системой типов решаются, а не синтаксисом языка.
Более близкими задачами будут:
- строить парсер по грамматике на этапе компиляции (ладно, в бусте аж 3 штуки есть, но вопросы времени компиляции и требуемую для этого память они как-то обходят стороной. Да и как выглядят ошибки я после быстрого просмотра документации не обнаружил).
- декларативно навешивать атрибуты на код, типа такого.
- да даже просто имена enum-ов получить.
- или ту же сериализацию многострадальную.
- логгинг — логгировать вход и выход из функции, с именем функции и параметров.
Все правильно, так и делаю. Только не каждый раз, а только для тех итераторов у которых реально есть RA. О том собственно и речь.
Ну со специализацией и не придется реализовывать вручную там, где не надо. В приводимых вами ссылках на std Rust-а она как раз и срабатывает на наличие типажа TrustedRandomAccess
, как я и говорил. И даже сам этот типаж на типе Zip<I1, I2>
реализуется, если оба итератора его реализуют, как вы и хотели.
А чо макросы не помогли да? Это же плагин компилятора, который может всё.
Он может все на синтаксическом уровне, т.е. писать код за вас. Но нарушить правила языка вам не поможет, скажем, побороть ненавидимый многими borrow checker не получится :).
Я предлагаю примеры на тему:
"что нельзя сделать с помощью плагина к компилятору, но можно сделать на шаблонах"
Вы предлагаете наоборот:
"что нельзя сделать с помощью шаблонов, но можно сделать на процедурных макросах"
Такой вот разговор получается)))
Я согласен, это должно друг друга дополнять в идеале.
Шаблоны можете заменить на более мощные дженерики если они появятся в расте.
Некоторые компиляторы умеет инструментировать для определение такие моменты, пусть в рантайме
Мой профессиональный опыт написания относительно современного С++ кода и аудита Rust-кода (включая Rust-код, который существенно использует unsafe) заключается в том, что безопасность современного С++ просто не сравнится с языками, в который безопасность памяти включена по умолчанию
Доработайте, пожалуйста, мысль. Безопасность в C++ всё же хорошая или плохая?
Современный C++ нас не спасет