Rust’s memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (known as a memory leak). Preventing memory leaks entirely is not one of Rust’s guarantees, meaning memory leaks are memory safe in Rust.
Так тут и написано, что утечку памяти невозможно выявить на этапе компиляции. По этому Rust не дает этих гарантий. И другие языки не дают.
По-этому вы не смогли обосновать что это "фича", ведь другие языки имеют точно такую же "фичу".
Циклические ссылки в любом языке могу привести к утечкам памяти. Даже GC не всегда может их заметить. Почему вы считаете это проблемой только Rust?
поэтому они считаются не ошибками, а фичами
Обоснуйте. Пока что это просто ваша фантазия, которую вы приписываете разработчикам языка Rust.
Тогда как UB в С++ (англ. undefined behavior) именно неопределенное поведение, т.е. это не зафиксировано в стандарте С++ и конкретная реализация оставлена на усмотрение разработчиков компилятора.
Вы категорически не правы (я очень надеюсь что просто заблуждаетесь). Undefined behavior не равно unspecified behavior, и в стандарте напрямую написано "код, содержащий UB, не является кодом на С++". В отличие от unspecified behavior.
Так оно не уменьшает количество кода. Давно известна шуточная поговорка: если переписать любую программу на ООП, она станет в 2 раза больше.
Даже в С++ уменьшает, несмотря на разделение на .cpp и .h. Вы, я вижу, ничего не слышали о таких принципах ООП как наследование и композиция, которые придуманы именно для уменьшения дублирования кода.
И как бы эти мелочи влияли на размер кода в разы?.. Кстати, enum даже в Си есть.
Это просто феерическое невежество! Enum в С это надстройка над int, а enum в Rust это тип-сумма (Sum Type), и он может внутри себя содержать другие типы. И это сильно уменьшает объявление структур например. Зачем вы обсуждаете язык Rust, о котором не знаете практически ничего?
Может быть, в среде конкретно Жабы и нормально считать, что "наша программа не содержит Data Race и также не содержит Race Condition" тупым volatile, но у нас в ядре за такое будут бить по рукам, назвав race condition.
Volatile в Java и Volatile в С или С++ это разные вещи, и они дают разные гарантии. Но вы как обычно не в курсе..
Ровно наоборот. Принципиальным является понимание что происходит, с выбором соответствующих мер, но это как раз в Rust исходят из религиозно-фанатичной позиции "что проверил компилятор, то безошибочно, давайте всё перепишем на раст", за что их и надо бить. Мы же знаем, что ошибаются все, поэтому ревьювить и тестировать надо всё.
Вы не понимаете разницы между кодом библиотек и кодом, использующим эти библиотеки. Для библиотек с примитивами синхронизации в любом случае нужно ревью, тесты и т.д.
Для кода, использующего эти библиотеки, компилятор С и С++ не может проверить, что библиотеки используются правильно. Да и вообще используются, можно забыть вызвать mutex, и код скомпилируется. По-этому приходится обмазываться ubsan, статическими анализаторами и т.д.
А вот компилятор Rust может проверить, и вам с этого очень обидно, и вы специально переводите разговор на другие темы.
Коим боком увеличение числа тредов в драйвере, совсем другой вопрос обсуждения, "замыливает" вопросы синхронизации? Ведите дискуссию корректно, не путайте ветки, ошиблись про число тредов в драйвере - не обвиняйте собеседника в вопросе, который обсуждается в цитате выше.
Не выдирайте слова из контекста. Обычно в драйвере 1 поток, а смысл был в том что кроме него есть прерывания и DMA. То что там в некоторых специфичных драйверах не один поток, ничего не меняет.
Так вполне очевидно из приводимых мной примеров в комментариях - это прежде всего код в ядре. А ядро - оно отнюдь не только из драйверов состоит.
Посмотрите исходники Redox OS например, и убедитесь наконец что количество unsafe в ядре ОС - это лично ваш стереотип.
Сравнивалось небось с кодом, в котором нет соответствующий библиотечных функций, которыми уже обеспечили в Расте. Сам язык не выглядит более выразительным, чем С++ (и более того, выглядит более verbose).
Вы сравнили С и Java, по-этому я написал про С. И библиотеки тут не при чем. Разве в С есть классы, итераторы, трейты/интерфейсы и прочее ООП? А в С++ сильно не хватает тип-суммы (Sum Type), в Rust это Enum. Ну и pattern matching, который завезли даже в С# уже.
Общепринятым термином является race condition. А "гонка данных" режет русское ухо, напоминая "добрый морнинг, господа телевотчеры" и т.п. кальку.
Race Condition (состояние гонки) и Data Race (гонка данных) это разные вещи, что должен бы знать каждый системный программист. Впрочем уже очевидно, что вы им не являетесь. А перевод на русский не я придумал, он общепринятый.
Вопрос был не в том, как они представляются программисту, а как они реализованы внутри, под капотом. А там чудес не бывает.
В ассемблере будет +- одно и то же. Но вопрос именно в том, чтобы код с Data Race не мог скомпилироваться. Особенно это важно в ядре Linux, по-этому Торвальдс старается продвинуть Rust в ядро.
А вы исходите из позиции: я идеальный программист и никогда не ошибаюсь.
В микроконтроллерах - запросто, а вот в драйверах зачастую далеко не так. Взять хотя бы RSS сетевых адаптеров.
Вопрос стоит как предотвратить Data Race, а вы его замыливаете.
То, что в каком-то подмножестве unsafe требуется редко (а микроконтроллеры, как и драйверы, не являются чем-то эссенциально сложным), не означает, что весь системный код будет являться таковым.
Микроконтроллеры и драйверы - это тот самый код, который (по общему убеждению) весь построен на unsafe. Но практика показывает, что это не так.
А весь остальной код почти не требует unsafe, т.к. выполняется поверх ОС. Вы уж сначала определитесь, что такое для вас системное программирование. Ато это переобувание в полете выглядит так себе..
к выразительным высокоуровневым языкам Раст не относится
От 2 до 4 раз меньше кода, чем в С.
Не знаю, я не пишу на всех перечисленных. И само словосочетание "гонка данных" по-русски режет ухо - что именно имеется в виду? В каждый момент структуру может borrow только один тред? Так это ж сразу "привет" производительности в ряде случае - никаких атомиков, только разрешенные компилятором мьютексы (а не любые), и т.д.
Что же вы не знаете общепринятые термины в системном программировании, про которое так смело рассуждаете? Гонки данных (data race), это один из видов состояния гонки. Приводит к багам, которые проявляются случайным образом, и которые очень трудно найти и отладить.
В Rust это решается трейтами Send и Sync, которые работают поверх концепции владения (borrow). И неверно написанный код не вызовет гонку данных, а просто не скомпилируется. В отличие от тех языков, на которых вы не пишете.
P.S. В драйвере или микроконтроллерах всего один поток. Но гонку данных вызвать очень легко из-за прерываний и DMA. И это большая проблема при разработке ядра Linux например.
Разве под словами "Так не проще ли взять любой современный язык с GC и JIT?" не предполагается именно "переписать"?
Нет, не предполагается. Переписывать на другой язык может иметь смысл только если принято решение этот легаси в любом случае переписать.
Я имел ввиду новый код, думал что это очевидно.
Предложенная техника защиты походит на концепцию владения и заимствования из языка Rust, но реализована на базе сильных и слабых ссылок (стандартных механизмов С++ shared_ptr и weak_ptr). Любые операции с данными для переменной по ссылке возможны только после её захвата, т. е. после преобразования слабой ссылки (weak_ptr) в сильную (shared_ptr).
Вот этот абзац поста меня запутал, выглядит что всё происходит в рантайме. А у вас на гитхабе написано нормально, так что про рантайм я был не прав.
Чтобы их не было, нужно использовать правильные классы и язык программирования тут не причем.
Если язык позволяет и поощряет писать неправильно, будут писать неправильно. И не будут использовать "правильные классы", ведь без них код "как бы проще и короче". Об этом вам уже написали выше в этом комментарии.
В Rust платность заключается в необходимости переписывать тонны существующего кода и это никак не связано с рантайм проверками.
Вы это сами только что придумали? Приведите цитату, где я предлагал что-то переписать на Rust!
Поэтому некоторые проверки всегда будут выполнятся в рантайме не зависимо от языка программирования.
Вот именно что некоторые проверки, причем довольно редкий случай для прикладного кода. А все прочие вы предлагаете сделать в рантайме вместо compile-time.
И я заметил как вы аккуратно забыли тему гонок данных и других UB, которые никак не предотвращает Memsafe, но предотвращает Borrow Checker, который вам так не нравится. Ведь вы изначально утверждали так:
Вся ценность Rust свелась к одному заголовочному файлу для C++
В Rust небесплатность заключается в том, что нужно указывать явно время жизни когда компилятор не может его вывести автоматически. Я вот сейчас посчитал, в текущем проекте на 50+KLOC таких строчек ровно 71 (0,14%), и это Embedded (микроконтроллеры STM32). Так что на практике отладка и поддержка не страдают.
Вообще я думал, что в С++ стараются избегать рантайм проверок (из-за чего в языке существуют многочисленные и неочевидные UB), а тут предлагается прямо противоположное.
Вероятно этот подход со слабыми ссылками будет по производительности даже хуже современного GC с аренами. Так не проще ли взять любой современный язык с GC и JIT?
P.S. В любом случае для С, С++ и Rust zero-cost проверки должны быть приоритетнее рантайм проверок, но мой взгляд.
Надеюсь вы понимаете, что слабые ссылки не бесплатные. И должно быть минимум 2 вида слабых ссылок: однопоточные и многопоточные. Первые почти бесполезные, вторые внутри реализованы через бесконечный цикл с atomic::compare_exchange_weak.
В Rust же концепция владения полностью zero-cost. Кроме того сильные/слабые ссылки сами по себе не предотвращают гонки данных.
Как я и говорил, у компилятора не достаточно данных для оценки времени жизни, ваша ссылка это только подтверждает) Прочитайте например раздел Access outside of lifetime.
Вообще весь документ описывает как нельзя делать и в каких случаях будет UB. Что прозрачно намекает что компилятор не может оценить времена жизни (за исключением когда явно срабатывает RAII), и перекладывает на программиста ответственность за времена жизни.
Теперь вопрос что же придумал Страуструп? Правильно ли я понимаю, что он предлагает явно определять времена жизни через атрибуты?
Вы скинули ссылку на времена жизни в С, по-этому я ответил про С.
Ну хорошо, перепишите мой пример на new/delete и ничего не поменяется. Как кстати и вопрос про замыкания: это невозможно на текущем этапе развития компиляторов.
а для профилей используются атрибуты.
Означает ли это, что время жизни будет задаваться явно через атрибут?
Ну это очень ограниченные времена жизни. Как это должно работать с замыканиями, или в указателями, сохраненными вместе с каллбеком?
Но даже с более простыми случаями могут возникнуть проблемы. Код ниже конечно странно написан, но, например, в любом lock-free алгоритме примерно весь код такой (ради быстродействия):
bool f(int* a, bool b) { bool b = ...; // что-то делаем с a if (b) free(a); } int* a = malloc(sizeof(int)) bool b = f(a); if (!b) { ... free(a); }
Не представляю, что тут сможет сделать компилятор с профилем lifetime, кроме как запретить такой код полностью.
Вот у меня сейчас в прошивке для микроконтроллера на 50KLOC ровно 2 unsafe (можно и без них, если вынести в библиотеку). В проекте всё на прерываниях, DMA, очередях, мютексах, RTOS, lock-free аллокаторы и т.д. Так что инкапсуляция вполне позволяет строить безопасное API в огромных кодовых базах.
При использовании библиотек преимущества Раста над этими языками опять же теряются.
Если ли я в этих 50KLOC случайно напишу гонку данных, то она не скомпилируется. Как с этим в С++, С# или Python например, не подскажете?)
Двухсвязный список очень специфическая структура данных, применяется ИМХО ровно в 2 областях: RTOS и кастомные аллокаторы. Причем глубоко внутри, во внешнем API его нет. Во всех остальных случаях достаточно вектора и однонаправленной очереди.
От дерева же никто не ждет идеальную производительность, и вполне допустимо сделать его на счетчиках ссылок без unsafe вообще.
16 бит вовсе не экзотическая платформа, и zlib-rs вполне под них компилируется.
Вообще у них обоих прямо не обозначен список поддерживаемых платформ, т.к. он зависит только от компилятора. Это значит что он примерно одинаковый, за исключением действительно экзотических платформ, натипа PowerPC или Spark.
По оптимизации в zlib-ng заявлен огромный список от SSE2 до AVX512-VNNI, а в zlib-rs пока только AVX512 и NEON. Вероятно он выигрывает на основных платформах, и проигрывает на всех старых.
P.S. Если же сравнивать чисто языки, то Rust должен быть чуть быстрее, т.к. система типов дает больше возможностей для оптимизаций. Но при этом до сих пор нет хвостовой рекурсии например, так что +- одинаково.
1) Добавили panic_handler в библиотеку core, чтобы код с паникой без реализации panic_handler просто не скомпилировался. 2) Согласовали с Торвальдсом набор ключей компиляции для ядра, которые заменяют панику в некоторых операциях (на что-то определенное, не UB), например переполнение целых чисел. 3) Добавили в core библиотеку методы с возвратом Result, там где их не хватало.
Он скорее говорил "такие то особенности Rust не применимы в ядре, по-этому в текущем виде я против Rust". В частности речь шла про обработку OutOfMemory error и недопустимость panic при некоторых операциях.
Сейчас эти вещи доработали (а некоторые исправили), и Торвальдс активно продвигает Rust в ядре.
Так тут и написано, что утечку памяти невозможно выявить на этапе компиляции. По этому Rust не дает этих гарантий. И другие языки не дают.
По-этому вы не смогли обосновать что это "фича", ведь другие языки имеют точно такую же "фичу".
Циклические ссылки в любом языке могу привести к утечкам памяти. Даже GC не всегда может их заметить. Почему вы считаете это проблемой только Rust?
Обоснуйте. Пока что это просто ваша фантазия, которую вы приписываете разработчикам языка Rust.
Вы категорически не правы (я очень надеюсь что просто заблуждаетесь). Undefined behavior не равно unspecified behavior, и в стандарте напрямую написано "код, содержащий UB, не является кодом на С++". В отличие от unspecified behavior.
Даже в С++ уменьшает, несмотря на разделение на .cpp и .h. Вы, я вижу, ничего не слышали о таких принципах ООП как наследование и композиция, которые придуманы именно для уменьшения дублирования кода.
Это просто феерическое невежество! Enum в С это надстройка над int, а enum в Rust это тип-сумма (Sum Type), и он может внутри себя содержать другие типы. И это сильно уменьшает объявление структур например. Зачем вы обсуждаете язык Rust, о котором не знаете практически ничего?
Volatile в Java и Volatile в С или С++ это разные вещи, и они дают разные гарантии. Но вы как обычно не в курсе..
Вы не понимаете разницы между кодом библиотек и кодом, использующим эти библиотеки. Для библиотек с примитивами синхронизации в любом случае нужно ревью, тесты и т.д.
Для кода, использующего эти библиотеки, компилятор С и С++ не может проверить, что библиотеки используются правильно. Да и вообще используются, можно забыть вызвать mutex, и код скомпилируется. По-этому приходится обмазываться ubsan, статическими анализаторами и т.д.
А вот компилятор Rust может проверить, и вам с этого очень обидно, и вы специально переводите разговор на другие темы.
Не выдирайте слова из контекста. Обычно в драйвере 1 поток, а смысл был в том что кроме него есть прерывания и DMA. То что там в некоторых специфичных драйверах не один поток, ничего не меняет.
Посмотрите исходники Redox OS например, и убедитесь наконец что количество unsafe в ядре ОС - это лично ваш стереотип.
Вы сравнили С и Java, по-этому я написал про С. И библиотеки тут не при чем. Разве в С есть классы, итераторы, трейты/интерфейсы и прочее ООП? А в С++ сильно не хватает тип-суммы (Sum Type), в Rust это Enum. Ну и pattern matching, который завезли даже в С# уже.
Race Condition (состояние гонки) и Data Race (гонка данных) это разные вещи, что должен бы знать каждый системный программист. Впрочем уже очевидно, что вы им не являетесь.
А перевод на русский не я придумал, он общепринятый.
В ассемблере будет +- одно и то же. Но вопрос именно в том, чтобы код с Data Race не мог скомпилироваться. Особенно это важно в ядре Linux, по-этому Торвальдс старается продвинуть Rust в ядро.
А вы исходите из позиции: я идеальный программист и никогда не ошибаюсь.
Вопрос стоит как предотвратить Data Race, а вы его замыливаете.
Микроконтроллеры и драйверы - это тот самый код, который (по общему убеждению) весь построен на unsafe. Но практика показывает, что это не так.
А весь остальной код почти не требует unsafe, т.к. выполняется поверх ОС. Вы уж сначала определитесь, что такое для вас системное программирование. Ато это переобувание в полете выглядит так себе..
От 2 до 4 раз меньше кода, чем в С.
Что же вы не знаете общепринятые термины в системном программировании, про которое так смело рассуждаете? Гонки данных (data race), это один из видов состояния гонки. Приводит к багам, которые проявляются случайным образом, и которые очень трудно найти и отладить.
В Rust это решается трейтами Send и Sync, которые работают поверх концепции владения (borrow). И неверно написанный код не вызовет гонку данных, а просто не скомпилируется. В отличие от тех языков, на которых вы не пишете.
P.S. В драйвере или микроконтроллерах всего один поток. Но гонку данных вызвать очень легко из-за прерываний и DMA. И это большая проблема при разработке ядра Linux например.
Нет, не предполагается. Переписывать на другой язык может иметь смысл только если принято решение этот легаси в любом случае переписать.
Я имел ввиду новый код, думал что это очевидно.
Вот этот абзац поста меня запутал, выглядит что всё происходит в рантайме. А у вас на гитхабе написано нормально, так что про рантайм я был не прав.
Если язык позволяет и поощряет писать неправильно, будут писать неправильно. И не будут использовать "правильные классы", ведь без них код "как бы проще и короче". Об этом вам уже написали выше в этом комментарии.
Вы процитировали так, что полностью согласны. И выглядит это очень агрессивно, тем более что это неправда. А теперь вы резко поменяли точку зрения:
Вы это сами только что придумали? Приведите цитату, где я предлагал что-то переписать на Rust!
Вот именно что некоторые проверки, причем довольно редкий случай для прикладного кода. А все прочие вы предлагаете сделать в рантайме вместо compile-time.
И я заметил как вы аккуратно забыли тему гонок данных и других UB, которые никак не предотвращает Memsafe, но предотвращает Borrow Checker, который вам так не нравится. Ведь вы изначально утверждали так:
В Rust небесплатность заключается в том, что нужно указывать явно время жизни когда компилятор не может его вывести автоматически. Я вот сейчас посчитал, в текущем проекте на 50+KLOC таких строчек ровно 71 (0,14%), и это Embedded (микроконтроллеры STM32). Так что на практике отладка и поддержка не страдают.
Вообще я думал, что в С++ стараются избегать рантайм проверок (из-за чего в языке существуют многочисленные и неочевидные UB), а тут предлагается прямо противоположное.
Вероятно этот подход со слабыми ссылками будет по производительности даже хуже современного GC с аренами. Так не проще ли взять любой современный язык с GC и JIT?
P.S. В любом случае для С, С++ и Rust zero-cost проверки должны быть приоритетнее рантайм проверок, но мой взгляд.
Надеюсь вы понимаете, что слабые ссылки не бесплатные. И должно быть минимум 2 вида слабых ссылок: однопоточные и многопоточные. Первые почти бесполезные, вторые внутри реализованы через бесконечный цикл с atomic::compare_exchange_weak.
В Rust же концепция владения полностью zero-cost. Кроме того сильные/слабые ссылки сами по себе не предотвращают гонки данных.
Как я и говорил, у компилятора не достаточно данных для оценки времени жизни, ваша ссылка это только подтверждает) Прочитайте например раздел Access outside of lifetime.
Вообще весь документ описывает как нельзя делать и в каких случаях будет UB. Что прозрачно намекает что компилятор не может оценить времена жизни (за исключением когда явно срабатывает RAII), и перекладывает на программиста ответственность за времена жизни.
Теперь вопрос что же придумал Страуструп? Правильно ли я понимаю, что он предлагает явно определять времена жизни через атрибуты?
Я гонюсь за производительностью по результатам профилирования. Мой опыт показывает, что узкое место очень редко будет связано с реализацией дерева.
Оппонент выше дал ссылку про времена жизни в С, по-этому я ему ответил про С. В С++ я разумеется не использую malloc.
Вы скинули ссылку на времена жизни в С, по-этому я ответил про С.
Ну хорошо, перепишите мой пример на new/delete и ничего не поменяется. Как кстати и вопрос про замыкания: это невозможно на текущем этапе развития компиляторов.
Означает ли это, что время жизни будет задаваться явно через атрибут?
Ну это очень ограниченные времена жизни. Как это должно работать с замыканиями, или в указателями, сохраненными вместе с каллбеком?
Но даже с более простыми случаями могут возникнуть проблемы. Код ниже конечно странно написан, но, например, в любом lock-free алгоритме примерно весь код такой (ради быстродействия):
bool f(int* a, bool b) {
bool b = ...; // что-то делаем с a
if (b) free(a);
}
int* a = malloc(sizeof(int))
bool b = f(a);
if (!b) {
...
free(a);
}
Не представляю, что тут сможет сделать компилятор с профилем lifetime, кроме как запретить такой код полностью.
Интересует как это по факту будет работать, ведь в языке нет информации о времени жизни. Будут вычислять по косвенным данным?
Вот у меня сейчас в прошивке для микроконтроллера на 50KLOC ровно 2 unsafe (можно и без них, если вынести в библиотеку). В проекте всё на прерываниях, DMA, очередях, мютексах, RTOS, lock-free аллокаторы и т.д. Так что инкапсуляция вполне позволяет строить безопасное API в огромных кодовых базах.
Если ли я в этих 50KLOC случайно напишу гонку данных, то она не скомпилируется. Как с этим в С++, С# или Python например, не подскажете?)
Двухсвязный список очень специфическая структура данных, применяется ИМХО ровно в 2 областях: RTOS и кастомные аллокаторы. Причем глубоко внутри, во внешнем API его нет. Во всех остальных случаях достаточно вектора и однонаправленной очереди.
От дерева же никто не ждет идеальную производительность, и вполне допустимо сделать его на счетчиках ссылок без unsafe вообще.
16 бит вовсе не экзотическая платформа, и zlib-rs вполне под них компилируется.
Вообще у них обоих прямо не обозначен список поддерживаемых платформ, т.к. он зависит только от компилятора. Это значит что он примерно одинаковый, за исключением действительно экзотических платформ, натипа PowerPC или Spark.
По оптимизации в zlib-ng заявлен огромный список от SSE2 до AVX512-VNNI, а в zlib-rs пока только AVX512 и NEON. Вероятно он выигрывает на основных платформах, и проигрывает на всех старых.
P.S. Если же сравнивать чисто языки, то Rust должен быть чуть быстрее, т.к. система типов дает больше возможностей для оптимизаций. Но при этом до сих пор нет хвостовой рекурсии например, так что +- одинаково.
Разумеется недопустим. И это устранили:
1) Добавили panic_handler в библиотеку core, чтобы код с паникой без реализации panic_handler просто не скомпилировался.
2) Согласовали с Торвальдсом набор ключей компиляции для ядра, которые заменяют панику в некоторых операциях (на что-то определенное, не UB), например переполнение целых чисел.
3) Добавили в core библиотеку методы с возвратом Result, там где их не хватало.
Он скорее говорил "такие то особенности Rust не применимы в ядре, по-этому в текущем виде я против Rust". В частности речь шла про обработку OutOfMemory error и недопустимость panic при некоторых операциях.
Сейчас эти вещи доработали (а некоторые исправили), и Торвальдс активно продвигает Rust в ядре.