Линус Торвальдс остался недоволен рядом моментов в использовании Rust для Linux


    В прошлом году разработчики ядра Linux предложили использовать Rust для нового встроенного кода. В марте 2021 года эта идея была частично реализована — в состав ветки linux-next, на которой будет базироваться Linux 5.13, включили начальный набор компонентов для разработки драйверов устройств на Rust.

    Тогда же была опубликована документация по использованию Rust в ядре Linux с практическими примерами. Изначально Торвальдс не выступал резко против нововведений (не делает этого он и сейчас). Заявил лишь, что нужно проверить, как все эти новинки работают на практике. Сейчас Линус Торвальдс провел рецензирование патчей с реализацией возможности создания драйверов на Rust для Linux и высказал ряд критических замечаний.

    Главной проблемой, по его мнению, является потенциальная возможность “паники ядра” в некоторых ситуациях. Это может быть нехватка памяти, когда операции динамического распределения памяти могут завершаться ошибкой. Торвальдс заявил, что такой подход в ядре принципиально недопустим. Он даже пошел дальше, сообщив, что если разработчики не осознают проблему, то он станет отклонять любой код, который попытается использовать такой подход. Разработчик, который занимается патчами, считает, что проблема существует, но она решаема.

    Вторая проблема, по словам Торвальдса — использование вычислений с плавающей запятой или 128-битными типами, что не является допустимым для таких окружений, как ядро Linux. Это даже более критично, чем предыдущий момент, поскольку базовая библиотека Rust представляет собой один большой blob — в ней нельзя запросить лишь избранные возможности, оставив остальные без внимания. Использовать приходится все подряд, так что предотвратить использование той либо иной проблемной функциональности не получится. Решить можно и эту проблему, но лишь путем внесения изменения в компиляторе rust и сопутствующие библиотеки, притом, что у команды нет четкой стратегии по реализации модульности библиотек языка.

    Разработчики предоставили пример драйвера, но Линусу он не понравился. Торвальдс заявил, что этот пример бесполезен, добавив совет создать новый драйвер, который решает реальную задачу, а не демонстрирует теоретические возможности.

    Как бы там ни было, но Rust-у в Linux явно быть. Так, корпорация Google уже заявила о намерении принять участие в инициативе по продвижению поддержки Rust в ядро Linux. Компания привела примеры целесообразности внедрения Rust для борьбы с проблемами, которые возникают из-за ошибок при работе с памятью. Представители компании также считают, что Rust вполне готов присоединиться к C, став еще одним языком разработки компонентов ядра Linux.

    Компания подготовила начальный прототип написанного на Rust драйвера для механизма межпроцессного взаимодействия Binder. Он, по мнению авторов этого проекта, даст возможность провести детальное сравнение производительности и безопасности реализаций Binder на C и Rust. Эта работа еще не завершена, но уже подготовлены прослойки для использования базовых абстракций функциональности ядра, которые нужны для работы Binder.

    Ну и еще один шаг со стороны Google — признание Rust в качестве языка разработки Android. Его добавили в список языков разработки Android для усиления защищенности последнего, плюс для продвижения приемов безопасного программирования и повышения выявления проблем при работе с памятью в Android. Около 70% из всех опасных уязвимостей, которые выявлены в Android, вызваны ошибками при работе с памятью. Использование Rust дает возможность снизить риск появления уязвимостей, которые вызваны ошибками при работе с памятью, включая обращение к области памяти после ее освобождения и выход за границы буфера.

    Selectel
    IT-инфраструктура для бизнеса

    Комментарии 140

      +32
      «Линус чем-то недоволен» — это состояние системы по умолчанию, зачем целая статья?
        +7

        Это такой шаблон сами знаете какой прессы: https://www.google.com/search?q=site:lenta.ru+%22остался+недоволен%22

          +66
          «Линус чем-то недоволен» — это состояние системы по умолчанию, зачем целая статья?

          И тем не менее, пока есть люди, которые критически воспринимают любые изменения — можно пользоваться GNU/Linux спокойно.

          Беспокоиться нужно будет тогда, когда любую «замечательную фичу» сообщество будет воспринимать розовыми соплями.
            +4
            Слава богу, Линус отслеживает изменения в жизни Линукса и не даёт всё пустить на самотёк с неизвестными результатами? )
            Ну и есть надежда, что в момент, когда Линус будет удовлетворён предложенными вариантами — это действительно окажется полезным и хорошим инструментом.
            Как вы думаете, что лучше для последующей жизни — получить 5 у преподавателя, которому плевать на предмет и студентов или «хор» от въедливого и требующего знаний. Ну то есть на выходе после кого есть шанс получить лучшего специалиста?
            +14
            признание Rust в качестве списка языка разработки Android

            Что это я прочитал сейчас?

              +31
              Переведенный на коленке ради баннера внизу пост.
                +5
                Я чет заметил, что сколько я не пишу в личку авторам корпоративных блогов портянки с косяками в переводах — еще ни разу не исправляли. уже для себя запомнил — корп блог — ctrl+enter можно не использовать.
                  +1

                  Зависит от редактора, аудиомания и рувдс исправляли то, что я скидывал

                    0
                    Ну у меня в «белом» списке пока только PVS-Studio.
                      0
                      А они часто что-то переводят?
                        0
                        Не, это уже к комментарию про исправления в принципе. комбинации переводы+корп блог нормальной не знаю)
              +17
              Как бы там ни было, но Rust-у в Linux явно быть. Так, корпорация Google уже заявила о намерении принять участие в инициативе по продвижению поддержки Rust в ядро Linux.

              Если в таком виде, который Линус раскритиковал то лучше не надо.


              Я не хейтер Rust, но его появление в ядре должно быть очень продуманным, а не х… к-х… к и в продакшн.

                +11

                128-битные типы являются штатными в языке. В своё время было принято решение, что 128-бит на уровне языка несёт в себе больше пользы, чем поддержка платформ, которые не могут реализовывать 128-бит.


                А ключевая проблема в том, что на 128-бит на многих платформах нельзя иметь атомики.


                Зная подход Rust'а к таким вопросам, адаптация к требованиям ядра будет долгой, неторопливой и на выходе будет офигенной.

                  +3
                  А какое преимущество несет 128-бит на уровне языка? Почему нельзя поместить этот тип в стандартную библиотеку?
                    0

                    Потому что все int'ы — это lang-items, т.е. типы, о которых знает компилятор. Это даёт возможности оптимизации (большие, чем для обычных типов), плюс использовать 'as' (1usize as u8), использовать для итераторов и т.д. Плюс атомарная поддержка записи/сохранения без специальной магии (я не про атомики, я про то, что a=42u812 либо в a, либо нет, причём целиком).

                      +3
                      Это даёт возможности оптимизации
                      Какие конкретно оптимизации? Я не знаю архитектур, в которых есть нативная поддержка 128-битных вычислений, но даже если такие и найдутся — что мешает ввести intrinsic-функции как в C++?
                      Плюс атомарная поддержка записи/сохранения без специальной магии
                      Эмм, атомарность чтения/записи памяти разного размера и выравнивания гарантируется архитектурой процессора. Что за гарантии тут предоставляет Rust?
                        +2

                        Rust не "предоставляет" их, rust их транслирует в удобства языка. Ряд операций в Rust не возможен для неродных типов (т.е. вы не можете пойти и изобрести (условный) u256, который будет вести себя абсолютно так же как u128). В первую очередь это касается `as и возможности писать их как константы.


                        Реализация u256 потребует написать что-то вида


                        #[derive(Copy, Clone)]
                        struct u256{
                            lower: u128,
                            upper: u128
                        }

                        и после этого вы потеряете возможность написать что-то вида let a = 1u256 или let b = 1u128 as u256.


                        Так что для u128 приняли решение включить его в стандарт языка.

                          +7

                          Какие-то костыли вокруг плохо продуманных трейтов. Кто мешает сделать как в хаскеле, где fromIntegral позволяет добавить поддержку литералов в произвольный числовой тип?

                            +3
                            Видимо в rust очень много накручено вокруг корректности многопоточности при отсутствии блокировок.

                            Если один поток делает
                            a = 1
                            a = -1
                            А второй поток делает
                            b = a
                            То тут без нативной поддержки u64 нельзя гарантировать, что в переменной b не окажется числа 0xFFFFFFFF00000001. Либо надо и чтение, и запись, закрывать под критическую секцию.
                              +7
                              Если один поток делает
                              a = 1
                              a = -1
                              А второй поток делает
                              b = a

                              А я в расте вообще могу так сделать без unsafe?

                                +2

                                Атомики заканчиваются на AtomicU64, так что думаю что нельзя.


                                К слову, и проблемы 128 бит я не понял. Основная претензия — паники — окей, могу понять. Что там в длинных интах не понравилось пока не дошло

                                  +8
                                  Что там в длинных интах не понравилось пока не дошло

                                  У вас есть 4 отдельных u32, которые надо поделить на степень двойки. Умный компилятор заменяет деление на сдвиг вправо. Ещё более умный компилятор пытается вам помочь, засунув их как единое 128-битное значение в SSE-регистр, чтобы потом выполнить векторную операцию сдвига.


                                  Но в ядре Linux нельзя пользоваться SSE-регистрами, потому что это дорого: если ядро будет трогать их значения, то их надо сохранять-восстанавливать при переключении контекста, чтобы не ломать пользовательские процессы. Дешевле ядру просто не пользоваться и не трогать.


                                  Код Linux на C пишут так, чтобы он не использовал эти инструкции и не приводил к их генерации. Код corelib в Rust, которая линкуется во всё, написанное на Rust, использует эти инструкции — через интринсики компилятора. Один из патчей заменяет эти интринсики на паникующие заглушки — на случай, если они всё же будут использованы, то чтобы это было заметно.


                                  Код на Rust для ядра, естественно, не будет пользоваться теми частями corelib, которые включают эти инструкции. (Среди которых есть и всякие для 128-битной арифметики через SSE.) Линусу не очень нравится такой подход, ему бы хотелось, чтобы такой код просто не компилировался. Но corelib ещё работает над этим. А пока что оставили такие заглушки.


                                  Но кто читает письма на рассылке? Ведь гораздо интереснее сраться в комментариях на темы, где u128 просто свечку держал.

                                    +1
                                    У вас есть 4 отдельных u32, которые надо поделить на степень двойки. Умный компилятор заменяет деление на сдвиг вправо. Ещё более умный компилятор пытается вам помочь, засунув их как единое 128-битное значение в SSE-регистр, чтобы потом выполнить векторную операцию сдвига.

                                    Что мешает сделать использование SSE опциональным параметром компилятора? Насколько я знаю это делается через компиляцию с "features": "-mmx,-sse,+soft-float". Никаких паникующих флоатов и SSE в итоге. Даже если кто и воспользуется — нормально отработает, мб чутка медленее чем с нативно поддержкой — ну и ладно.

                                      –6
                                      Но в ядре Linux нельзя пользоваться SSE-регистрами, потому что это дорого: если ядро будет трогать их значения, то их надо сохранять-восстанавливать при переключении контекста, чтобы не ломать пользовательские процессы. Дешевле ядру просто не пользоваться и не трогать.
                                      Значит это плохое ядро. Всего навсего.
                                        +1
                                        Возможно. Напишите своё, хорошее, на чистом Расте. Тут же вопрос, можно ли Раст, с его SSE-манипуляциями, пускать в «плохое» ядро.
                                        +1
                                        Но кто читает письма на рассылке? Ведь гораздо интереснее сраться в комментариях на темы, где u128 просто свечку держал.

                                        Чем плохо использовать SSE в ядре, я и сам понимаю (хотя интересно, что делают опции вроде AVX2-accelerated foobar crypto algorithm в моём make menuconfig). Мне было интересно, что в философии раста влияет на возможность реализовать это библиотечным образом, без всякой привязки к ядру, раз уж пошла такая пьянка.

                                          0
                                          А префикс LOCK может применяться к store-инструкциям SSE?
                                          Тогда atomic-операции с u128 могут выражаться через него.
                                            0
                                            А префикс LOCK может применяться к store-инструкциям SSE?

                                            Неа (насколько я знаю). Можно проверить, написав простенькую программку с lock movaps и посмотрев, стриггерится ли #UD, но я сейчас с телефона.

                                            +3
                                            хотя интересно, что делают опции вроде AVX2-accelerated foobar crypto algorithm в моём make menuconfig

                                            Ядру нельзя в общем случае пользоваться SSE. Если использование контролированное, то можно, конечно.


                                            Код из arch/x86/crypto заботливо сохраняет на стеке все XMM-регистры, которые он трогает, а потом восстанавливает обратно, и всё это делается между kernel_fpu_begin() и kernel_fpu_end(), которые отключают preemption, чтобы другой поток не отобрал процессор, пока он там шаманит с регистрами.


                                            Но это ассемблер, там это делается вручную. Компилятору Си не объяснишь, какие регистры надо сохранять, какие не надо, а какие не трогать, когда прерывания отключать, а когда не надо, и так далее. Компилятору Раста в общем случае тоже. Это надо вводить в язык понятие распределения регистров и для каждой функции описывать её особенную уникальную call convention.

                                    +1

                                    From и Into есть в полном объёме. Но сделать (например) let a = 0x7c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1 вы не сможете. Для 'as' сделано специальное требование "понимать компилятором", потому что as — гарантировано дешёвая операция и поддерживается только там, где это дёшево и правильно. Для всех остальных вариантов — пишите своё. Но родные типы всё-таки роднее, потому что с чего-то надо начинать.


                                    Хаскелю с чего-то начинать не нужно, потому что у него есть волшебный рантайм, в котором и GC и чёрти что, вместо проблемы "не хотим на кучу".

                                      +1
                                      Но сделать (например) let a = 0x7c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1 вы не сможете.

                                      Почему не смогу?


                                      Для 'as' сделано специальное требование "понимать компилятором", потому что as — гарантировано дешёвая операция и поддерживается только там, где это дёшево и правильно.

                                      Тогда как она выглядит для 128-битных типов? И почему нельзя потребовать от авторов библиотек делать их инстансы для as эффективными? Конечно, это ничего не гарантирует, но и библиотеками можно не пользоваться.


                                      Хаскелю с чего-то начинать не нужно, потому что у него есть волшебный рантайм, в котором и GC и чёрти что, вместо проблемы "не хотим на кучу".

                                      GC ортогонален этим фичам. Даже этот наш fromIntegral, который Integer -> a, где Integer — длинная арифметика, для известных во время компиляции констант вполне может выполняться же во время компиляции. Собственно, мы ведь и говорим о вещах, которые могут (и должны) вырезаться во время компиляции, поэтому это вопрос исключительно их типизации.

                                        0

                                        Потому что компилятор не позволяет изменять литералы компилятора. Можно написать макрос, но удобство сильно упадёт.


                                        Почему нельзя менять литералы компилятора с помощью куска языка? Потому что компилятор может быть на другой архитектуре, чем целевая программа.


                                        Для 128-битных типов поддержка реализована в самом компиляторе (lang item).


                                        Во время компиляции код не может исполняться, потому что компилирую я на x86_64, а целевая система у меня, например, thumbv6m-none-eabi.

                                          +1
                                          Потому что компилятор не позволяет изменять литералы компилятора.

                                          Не уверен, что понимаю смысл этого утверждения. Зачем тут менять литералы?


                                          Во время компиляции код не может исполняться, потому что компилирую я на x86_64, а целевая система у меня, например, thumbv6m-none-eabi.

                                          И в чём проблема? Исполняйте код в режиме интерпретатора, как делает хоть хаскелевский template haskell, хоть плюсовые constexpr/consteval. Для того, чтобы распарсить литерал в компилтайме, этого более чем достаточно.

                                            0

                                            Вот программа:


                                            fn main(){
                                                let a = 0x7c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1f07c1;
                                            }

                                            Она содержит в себе невалидный литерал (256-битный int).

                                              +2
                                              А что в нем не валидного? Вот я писал свою библиотеку на С++, для своих самописанных 128-битных литералов:
                                              constexpr auto oct = 0376'67135230'35452062'04177334'56514166'25031020_ui128;
                                              constexpr auto dec = 338770000845734292534325025077361652240_ui128;
                                              constexpr auto hex = 0xfedcba98'76543210'fedcba98'76543210_ui128;
                                              
                                              static_assert(oct == dec && oct == hex);
                                              Если литерал использует «родной» синтаксис то все работает. Что мешает rust сделать также?
                                                0

                                                То, что ваши примеры u128, а мой — u256 (если калькулятор питона не подвёл).

                                                  +2
                                                  Да 128-бит. Но этот пример я привел просто в качестве демонстрации подхода. На моем компиляторе С++ нет нативных 128-битных целых, но он позволяет их реализовать на уровне библиотеки. Можно и с 256-битными работать, да хоть с 1024-х битными:
                                                  constexpr auto hex = 0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210_ui256;

                                                  Это всё равно валидный код на С++. Почему нельзя такой же подход использовать в rust?
                                                    0

                                                    Интересный вопрос. Я могу только гадать. Возможно, потому что язык не хочет разрешать модифицировать себя силами библиотек (за пределами макросов).


                                                    В принципе, это бы (я предполагаю) совпадает с запретом на модификацию чужих трейтов для чужих типов. Формально ничего не мешает, но запрет даёт определённые гарантии как авторам (что им странного не сделают), так и читателям кода (написанному верить, никаких #define TRUE FALSE).

                                                      +1

                                                      В C++ пользовательские литералы — это обычные функции с синтаксическим сахаром, и ничего внезапно переопределить не могут (и да, их как и любую функцию можно помещать в namespace). Два одинаковых литерала приведут к ошибке компиляции вида "функция с такой сигнатурой уже была объявлена выше".


                                                      Объявление литерала из примера Videoman может выглядеть, например, так:


                                                      constexpr my_uint256 operator ""_ui256(const char *n);
                                                        0

                                                        Ну во-первых в rust не разрешают выполнять "что попало" в compile time (без макросов). Насколько я понимаю, это часть политики.


                                                        Во-вторых, вроде бы, полный constexpr в Rust не завезли, если верить вот этому: https://github.com/rust-lang/rust/issues/57563

                                                          +1

                                                          Насколько я понял, вот этот const fn как раз является полным аналогом плюсового constexpr (в котором тоже совсем произвольный код не может быть), и задача "распарсить массив символов и сконструировать из результата структуру" либо уже решаема, либо требует небольших доработок в const fn, но уж точно не противоречит идеологии rust.


                                                          Так что и возможность определять пользовательские литералы — вопрос только введения соответствующего синтаксического сахара.

                                                0

                                                Так про это и вопрос: из-за каких фундаментальных причин он невалидный, и почему нельзя вынести обработку этих вещей из ядра на уровень библиотек?

                                                  0

                                                  Потому что если ваша архитектура (под которую ядро собирается) не умеет 128-числа, то вы не можете так написать. Вы можете сделать только mem::copy, что совершенно не то, что подразумевается в случае присвоения (нет атомарности).

                                                    +1

                                                    А почему вы считаете, что все инты атомарны? Ещё каких то 15 лет назад даже самый обычный инт32 но с неправильным выравниванием ни на одной платформе не был атомарным, теперь почти везде атомарный. Я это к тому что тому же с/с++ эта (не)атомарность особо никогда не мешала. Это я к тому что ну будет инт128 не атомарным из коробки и чё? В чем собственно проблема?

                                                      +1

                                                      Если произойдёт прерывание между записью первой половины инта и второй половиной, то в памяти окажется кадавр. Если код будет будет вызван повторно (из-за скедулера или внутри обаботчика прерывания), то он увидит кадавра.


                                                      Для удобства, предположим, что у нас есть только u8, а мы изображаем u16 из двух половинок.


                                                      Было: 0xFFFF
                                                      Хотим записать 0x0


                                                      Правила обработки:


                                                      0xFFFF — пусто
                                                      0x0000 — требуется обработка
                                                      0xFF00 — аварийное завершение


                                                      Дальше мы пытаемся заменить 0xFFFF на 0x0000, записываем 0xFF, тут приходит прерывание (устройство), потом приходит прерывание (таймер), обработчик таймера запускает скедулер, скедулер смотрит "о, 0xFF00, давай-ка я его грохну". И наша вторая инструкция, которая должна была дописать 0x..FF так и не выполнилась.


                                                      Я очень утрирую, конечно, но проблема в этом.

                                                        +1

                                                        Это и так понятно, эта проблема была всегда, сегодня для 128, вчера для 64, позавчера для 32 и так до ламповых компьютеров, ничего принципиально не поменялось. Си уже наверное пережил эту проблему начиная с 16 и так далее и на уровне языка это ничему не мешает, и тут вдруг для раста это стало проблемой. Вы так и не сказали что перестанет работать если Раст перестанет гарантировать атомарность для 128.

                                                          +4
                                                          если Раст перестанет гарантировать атомарность для 128

                                                          Если что, Rust не гарантирует атомарность ни для u128, ни для u64, u32, или u16. Эти типы можно изменять только через &mut T, который можно получить либо если значением владеет текущий поток, либо через какую-нибудь Sync-обёртку вроде Mutex, которая уже со своей стороны обязана гарантировать, что другие потоки не увидят частично выполненные записи.


                                                          А вот AtomicU128 вообще нет, потому что платформ нет, которые это умеют. Появятся — на них будет доступен.

                                                            0
                                                            Если что, Rust не гарантирует атомарность ни для u128, ни для u64, u32, или u16
                                                            Попробую предположить, что «атомарность» это гарантии std::atomic, когда операции чтение-инкремент-запись, или чтение-сравнение-запись выполняются атомарно. А тут речь не об этой атомарности, а только об одной операции чтения или записи.
                                                            0

                                                            Атомики касаются межпроцессорного взаимодействия. Всякие Acquire-release и т.д. Тут же речь про атомарность записи в рамках самого аппаратного треда. Грубо говоря, может между записью первой половины и второй произойти прерывание или нет.

                                                              +2

                                                              Давайте предположим, что я как бы это все читаю в первый раз в жизни и для меня это откровение. Правда я как бы немного буду чуть более наблюдательным чем если бы это было на самом деле так. Один из вас пишет, что Раст собственно ничего и не гарантирует, а другой пишет что речь идёт об аппаратной атомарности. Вы знаете я чёто запутался в ваших показаниях. Если Раст ничего не гарантирует, то на кой лят этот инт128, а если он что то гарантирует, то что же именно? И все же, а что сломается если просто тупо взять и переписать инт128 таким же образом как сейчас придётся писать инт256?


                                                              Дополнительный вопрос со звёздочкой, а как по вашему с проблемой не атомарности записи в инт большего чем регистр размера справляется ядро? Ну ведь не может же такого быть чтобы такой задачи ещё не возникало или что ядреные разработчики расплакались и сказали что это невозможно?

                                                                0

                                                                Я не знаю как Rust реализует u128 сейчас.


                                                                Я полез читать, ответ — LLVM integers: https://llvm.org/docs/LangRef.html#integer-type


                                                                Почему при этом нет u256 — вопрос интересный, потому что LLVM поддерживает вплоть до i8388607.


                                                                Получается, что u128 ничего с атомарностью записи (даже в пределах аппаратного треда). Более того, это означает, что в общем случае, в ядре с rust'ом даже u64 использовать нельзя.


                                                                На вопрос со звёздочкой, внезапно, я знаю примерный ответ: есть механизм RCU, который позволяет менять структуру при конкруентном доступе без блокировки. Плюс, если очень нужно, код может запрещать прерывания (в этом случае гарантируется отсутствие проблемы реэнтерабельности, если модифицируются только thread local перменные //как при этом решают проблему NMI не знаю). Для кросс-процессорных переменных там целое безумство имени linux memory model, которое базируется на наименьшем общем кратном всех безумств всех вендоров (кроме, вроде бы, Alpha, которая прогибается под linux ценой производительности).

                                                                  +1
                                                                  как по вашему с проблемой не атомарности записи в инт большего чем регистр размера справляется ядро?

                                                                  Очень просто: ядро ожидает, что разработчики компетентны. Расставляют всякие WRITE_ONCE(), где надо, чтобы компилятор не наоптимизировал чего-нибудь не того. Используют atomic_t, чтобы записать что-то атомарно. Если нужно записать ещё больше данных, то думают, каких барьеров куда понатыкать, чтобы при чтении увидеть согласованные данные.

                                                                  +2

                                                                  Железка, которая смотрит в свой регистр, или обработчик прерывания — это «другой поток» с точки зрения модели памяти. Так что атомарные операции тут вполне при чём.


                                                                  И вот в модели памяти Rust запись u64 не обязана быть атомарной. (Формальной модели памяти у языка нет, но фактически это модель LLVM.) И вот LLVM не даёт гарантий, что все восемь байтов i64 будут записаны все вместе. Он вообще может, если захочет, записывать байты поштучно и оставаться «технически» правым, в рамках своей модели.


                                                                  Для атомарных записей — чтобы обработчики прерваний, обработчики сигналов, и другие потоки видели запись целиком, а не кусочки байтов — у LLVM есть свои интринсики (__atomic_*()), которые генератор LLVM IR должен расставлять там, где он хочет атомарные записи. Для u64 они не вставляются, разумеется, и кодогенератор может творить что хочет.

                                                                    0

                                                                    Про LLVM'ные инты я уже увидел, да, спасибо.


                                                                    … Получается, что в ядре с Rus'ом всё ещё сложнее и нежнее, чем казалось.

                                                                      0
                                                                      И вот LLVM не даёт гарантий, что все восемь байтов i64 будут записаны все вместе. Он вообще может, если захочет, записывать байты поштучно и оставаться «технически» правым, в рамках своей модели
                                                                      Тогда как работают lock-free алгоритмы в C++, где запись/чтение указателя считается атомарной?
                                                                        +3

                                                                        Заворачивая указатели в std::atomic, разумеется, или не допуская одновременного доступа к ним. Доступ к одному и тому же значению «одновременно» из разных потоков без std::atomic — это гонка данных. Гонка данных — неопределённое поведение. Так что либо std::atomic на сам указатель, либо std::atomic на какой-то флажок или счётчик рядом, через который синхронизируется доступ к указателю.

                                                                +6
                                                                Ещё каких то 15 лет назад даже самый обычный инт32 но с неправильным выравниванием ни на одной платформе не был атомарным, теперь почти везде атомарный.

                                                                Чтение невыровненного даже int32 даже на x86 и сегодня неатомарное.

                                                                0

                                                                Почему не могу-то в теории? В принципе, выше уже написали про аналогию с user-defined literals в плюсах (да и я на неё пару раз намекал с парсингом в компилтайме) — там в рантайме вообще никаких копий, ничего нет.

                                              +1

                                              Компилятор раста слегка современнее плюсового, но даже в плюсовых не вижу проблем оптимизации «библиотечных» типов при должном желании.
                                              Атомик зависит не только от языка, а и от процессора. Если речь о реально эффективном атомарном доступе.

                                                +1
                                                Компилятор раста слегка современнее плюсового

                                                а вот с этого поржал) начиная с того что "компилятор" раста это всего лишь фронтенд на llvm и заканчивая тем что последний стабильный релиз gcc вышел неделю назад

                                                  +3

                                                  Смысл не в датах, а в фичах, системе типов, и так далее.

                                                    –2

                                                    И давно система типов в rust стала фактически самостоятельным языком для compile time как шаблоны в c++?

                                                      +3

                                                      А давно вы пытались написать на шаблонах C++ что-то чуть сложнее, чем компилтайм-факториал?


                                                      Язык для компилтайм лучше иметь, во-первых, тем же самым, что и рантайм-язык, а не самостоятельным, а, во-вторых, специально для этого разработанным, а не «ой, оказывается, алгоритм разрешения инстансов шаблонов застрял в тьюринговском болоте».

                                                        –1

                                                        тут вопрос не про необходимость этого или полезность вообще, а про мощную "систему типов" которая выросла в полноценный язык и где rust нервно курит в сторонке со своей типизацией. Так что, что даты сравнивать что фичи, rust и C++ даже не в одной весовой категории, это разговор ни о чём. IMHO его гораздо лучше бы восприняли не пытайся rust comunity делать абсурдные заявления в стиле "rust новая замена C++"

                                                          +6

                                                          А как вы собираетесь мощность без полезности обсуждать?


                                                          На мой взгляд, возможность выражать на уровне типов лайфтаймы, или же проверяемые до (а не после, как в плюсах) мономорфизации полиморфные функции — куда важнее на практике, чем многоэтажные темплейты. Тем более, что над вычислениями во время компиляции в расте тоже работают, насколько я знаю (сам-то я не растоман).

                                                            –2

                                                            Именно, ключевое слово "работают", этого в языке нет.
                                                            От нечего делать спрошу, каким временем жизни можно управлять через типы в rust по мимо расположения на стеке, в куче и в одном из "умных" указателей? И чего там можно проверить до мономорфизации? В c++ для этого есть концепты и sfinae которому уже "сто" лет

                                                              +4
                                                              От нечего делать спрошу, каким временем жизни можно управлять через типы в rust по мимо расположения на стеке, в куче и в одном из "умных" указателей?

                                                              Например, там можно выразить «эта переменная живёт не больше, чем та». Ну и все ништяки от borrow checker'а, которого в плюсах нет и не может быть (без потери обратной совместимости).


                                                              И чего там можно проверить до мономорфизации? В c++ для этого есть концепты и sfinae которому уже "сто" лет

                                                              Компилятор плюсов не требует использовать sfinae (которые, к слову, не совсем для этого) и концепты, и не проверяет, что тело функции им соответствует, даже если они есть.


                                                              Иными словами, объявление


                                                              template<typename T>
                                                              T foo(T a, T b)
                                                              {
                                                                return a + b;
                                                              }

                                                              вполне себе примется компилятором, хотя в типе функции ничего про сложение нет. Равно как и


                                                              template<typename T, typename = decltype(declval<T>() + declval<T>())>
                                                              T foo(T a, T b)
                                                              {
                                                                return a + b;
                                                              }

                                                              примется тайпчекером независимо от того, что выражены не все условия на operator+ (например, он должен возвращать тип, неявно конвертируемый в T, что здесь не написано).

                                                                0
                                                                Компилятор плюсов не требует использовать sfinae (которые, к слову, не совсем для этого) и концепты

                                                                Во-первых зачем он их должен требовать, они далеко не везде нужны? Во-вторых он проверит ровно то что ты от него требуешь. В-третьих что ты вообще пытаешься проверить тут?


                                                                template<typename T, typename = decltype(declval<T>() + declval<T>())>
                                                                T foo(T a, T b)
                                                                {
                                                                  return a + b;
                                                                }
                                                                  +5
                                                                  Во-первых зачем он их должен требовать, они далеко не везде нужны?

                                                                  Почему нет? Я хочу как можно раньше знать, что код, который я написал, не имеет смысла. Если это шаблонный код — то до его инстанциирования тем типом, на котором он сломается.


                                                                  В-третьих что ты вообще пытаешься проверить тут?

                                                                  Что тип T поддерживает те операции, которые от него требуются в теле функции.

                                                                    –2
                                                                    Я хочу как можно раньше знать

                                                                    Так добавь эти проверки там где они нужны, из-за то того что они нужны тебе не значит что они нужны всем и везде, язык не для кого то одного создаётся у каждого свои задачи и потребности.


                                                                    Что тип T поддерживает те операции, которые от него требуются в теле функции.

                                                                    И что же мешает это сделать?


                                                                    template <typename A>
                                                                    concept Summable = requires(A a, A b)
                                                                    {
                                                                        a + b;
                                                                    };
                                                                    
                                                                    template<Summable T>
                                                                    T sum(T a, T b){
                                                                        return a + b;
                                                                    }
                                                                      +5
                                                                      Так добавь эти проверки там где они нужны, из-за то того что они нужны тебе не значит что они нужны всем и везде, язык не для кого то одного создаётся у каждого свои задачи и потребности.

                                                                      Я не могу их добавить так, чтобы они проверялись в момент тайпчекинга шаблонной функции, а не кода, её инстанциирующего. Вообще. Никак.


                                                                      Ну и да, если там функция не на одну строку, а на 100 строк, то что вы будете делать, чтобы проверить, что вы всё, что нужно, указали в концептах, и сделали это правильно?


                                                                      И что же мешает это сделать?

                                                                      Вы здесь ошиблись, кстати — в Summable вы не проверяете, что тип выражения a + b может быть преобразован в A, поэтому этого концепта недостаточно для выражения всех требований.

                                                                        –4

                                                                        ты вообще на C++ пишешь? если результат не может быть сконвертирован в А, то ты вывалишься так же на этапе поиска шаблона, так как твоя функция уже говорит о том что результат сложения должен быть типом A или приводимым к нему.

                                                                          +3

                                                                          Я так понимаю, что вопросов о невозможности сделать проверки в точке определения полиморфной функции больше нет, и осталось дообсудить конкретный код?


                                                                          ты вообще на C++ пишешь?

                                                                          К счастью, уже год как нет. Что не мешает, впрочем, знать плюсы, включая свежие, лучше, чем по крайней мере некоторые из пишущих.


                                                                          если результат не может быть сконвертирован в А, то ты вывалишься так же на этапе поиска шаблона, так как твоя функция уже говорит о том что результат сложения должен быть типом A или приводимым к нему.

                                                                          Ага. Ну давайте проверим такой код:


                                                                          template <typename A>
                                                                          concept Summable = requires(A a, A b)
                                                                          {
                                                                              a + b;
                                                                          };
                                                                          
                                                                          template<Summable T>
                                                                          T sum(T a, T b){
                                                                              return a + b;
                                                                          }
                                                                          
                                                                          struct Bad {};
                                                                          
                                                                          int operator+(Bad, Bad) { return 42; }
                                                                          
                                                                          int main()
                                                                          {
                                                                              sum(Bad{}, Bad{});
                                                                          }

                                                                          clang 11 ругнётся примерно так:


                                                                          prog.cc:9:12: error: no viable conversion from returned value of type 'int' to function return type 'Bad'
                                                                              return a + b;
                                                                                     ^~~~~
                                                                          prog.cc:18:5: note: in instantiation of function template specialization 'sum<Bad>' requested here
                                                                              sum(Bad{}, Bad{});
                                                                              ^
                                                                          prog.cc:12:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const Bad &' for 1st argument
                                                                          struct Bad {};
                                                                                 ^
                                                                          prog.cc:12:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'Bad &&' for 1st argument
                                                                          struct Bad {};
                                                                                 ^
                                                                          1 error generated.

                                                                          in instantiation, как и ожидалось. Ничего никуда не отбросилось.


                                                                          Если же взять концепт, предложенный Antervis'ом ниже, то тогда да, всё будет работать на этапе поиска шаблона:


                                                                          prog.cc:19:5: error: no matching function for call to 'sum'
                                                                              sum(Bad{}, Bad{});
                                                                              ^~~
                                                                          prog.cc:9:3: note: candidate template ignored: constraints not satisfied [with T = Bad]
                                                                          T sum(T a, T b){
                                                                            ^
                                                                          prog.cc:8:10: note: because 'Bad' does not satisfy 'Summable'
                                                                          template<Summable T>
                                                                                   ^
                                                                          prog.cc:5:25: note: because type constraint 'std::convertible_to<int, Bad>' was not satisfied:
                                                                              {lhs + rhs} -> std::convertible_to<T>;
                                                                                                  ^
                                                                          /opt/wandbox/clang-head/include/c++/v1/concepts:171:3: note: because 'is_convertible_v<int, Bad>' evaluated to false
                                                                            is_convertible_v<_From, _To> &&
                                                                            ^
                                                                            +2
                                                                            ты вообще на C++ пишешь?
                                                                            Это было смешно…
                                                                            Вы в профиль собеседника зайдите, ради интереса.
                                                                              0
                                                                              эмм… я скорее ожидал что саркастический тон будет очевиден, но видимо ошибся. У меня есть предположение что это за чел, не так уж много хаскелистов на плюсовых конференциях, но я его акк на habre не знаю. Но если я угадал, то он сам тот ещё «троль», и не думаю что мой вопрос был воспринят всерьёз.
                                                                    +1
                                                                    примется тайпчекером независимо от того, что выражены не все условия на operator+ (например, он должен возвращать тип, неявно конвертируемый в T, что здесь не написано).
                                                                    Во-первых, вы могли написать как-то так и все ваши требования будут выполнены. Во-вторых, получается очередной спор на тему что лучше — трейты или концепты, где фундаментальная разница в том, что у концептов шире функционал (за счет «ленивости» проверок можно работать с типами, которые не знают о соответствующих концептах, а трейты лучше тем, что требования для типа задаются явно… однако с концептами такая явность достигается простейшим:
                                                                    static_assert(MyConcept<MyTypeToCheck>);
                                                                    Так что как раз с точки зрения проверок типов с++ мощнее. Вот чего не хватает по сравнению с rust — возможности всяких #[derive(Serialize, Deserialize)], что приедет с рефлексией. Собственно, предлагаю вам в ваших дальнейших спорах «rust vs c++» использовать именно этот аргумент
                                                                      +4
                                                                      Во-первых, вы могли написать как-то так и все ваши требования будут выполнены.

                                                                      Конечно, мог бы. Но я не обязан так делать, и в этом проблема.


                                                                      где фундаментальная разница в том, что у концептов шире функционал (за счет «ленивости» проверок можно работать с типами, которые не знают о соответствующих концептах)

                                                                      Ленивость проверок вообще никак не связана с подобной расшрияемостью. В хаскеле вон проверки не ленивые, но тайпклассы никто не мешает писать пост-фактум для типов, которые о них ничего не знают.


                                                                      однако с концептами такая явность достигается простейшим:

                                                                      …которое снова проверяется только тогда, когда функция инстанциируется для конкретного типа.


                                                                      Так что как раз с точки зрения проверок типов с++ мощнее.

                                                                      В том же смысле, в котором питон с точки зрения проверок типов мощнее C++.

                                                                        –2
                                                                        Конечно, мог бы. Но я не обязан так делать, и в этом проблема.
                                                                        вы всегда можете требовать от с++ кода любой степени привередливости по вашему усмотрению. А вот от раста большей выразительности уже нет.
                                                                        Ленивость проверок вообще никак не связана с подобной расшрияемостью. В хаскеле вон проверки не ленивые, но тайпклассы никто не мешает писать пост-фактум для типов, которые о них ничего не знают.
                                                                        а если вы не можете управлять ни тайпклассами, ни типами? Ну то есть пытаетесь засунуть сущность из внешней библиотеки A в метод другой внешней библиотеки B?
                                                                        …которое снова проверяется только тогда, когда функция инстанциируется для конкретного типа.
                                                                        не функция, а концепт, и проверяется сразу. Соответственно любой метод, принимающий тип, удовлетворяющий MyConcept, примет тип MyTypeToCheck.
                                                                        В том же смысле, в котором питон с точки зрения проверок типов мощнее C++.
                                                                        питон в принципе не способен на проверки времени компиляции сложнее удовлетворения синтксису, так что этот ваш аргумент попросту абсурден.
                                                                          +4
                                                                          вы всегда можете требовать от с++ кода любой степени привередливости по вашему усмотрению.

                                                                          Требовать-то я могу что угодно, вопрос в том, возможно ли этим требованиям удовлетворить.


                                                                          Во-первых, в данном случае я требую не от C++-кода, а от C++-компилятора, и тут с удовлетворением уже сложнее.
                                                                          Во-вторых, я бы посмотрел на код, читающий число из файла, и передающий это число в другую функцию, которая статически требует наличия проверок на то, что число является простым (это кивок в сторону завтипов, если что).


                                                                          а если вы не можете управлять ни тайпклассами, ни типами? Ну то есть пытаетесь засунуть сущность из внешней библиотеки A в метод другой внешней библиотеки B?

                                                                          Хаскель тут отличается от раста, в хаскеле orphan instances — всего лишь ворнинг. Я просто сажусь и пишу в своём коде инстанс тайпкласса из библиотеки B для типа из библиотеки A.


                                                                          Но даже если бы такой возможности не было, я бы сделал newtype-обёртку (не имеющую никакой стоимости в рантайме) и написал бы все нужные инстансы для неё:


                                                                          import qualified Some.Library as A
                                                                          import qualified Other.Library as B
                                                                          
                                                                          newtype SomeWrapper = SomeWrapper { getWrapped :: A.SomeType }
                                                                          
                                                                          instance B.Class SomeWrapper where
                                                                            ...

                                                                          не функция, а концепт, и проверяется сразу

                                                                          Нет, не сразу (то есть, не в момент написания функции), а в момент её вызова. Тут концепты вообще ничем не отличаются от SFINAE.


                                                                          питон в принципе не способен на проверки времени компиляции сложнее удовлетворения синтксису, так что этот ваш аргумент попросту абсурден.

                                                                          Точно так же, как и компилятор C++ принципиально не способен проверить тело функции до её инстанциирования (полностью — про синтаксическую проверку и разрешение non-dependent names я знаю).

                                                                            –1
                                                                            Во-вторых, я бы посмотрел на код, читающий число из файла, и передающий это число в другую функцию, которая статически требует наличия проверок на то, что число является простым (это кивок в сторону завтипов, если что).
                                                                            либо я вас не понял, либо это делается оберткой над числом
                                                                            Но даже если бы такой возможности не было, я бы сделал newtype-обёртку (не имеющую никакой стоимости в рантайме) и написал бы все нужные инстансы для неё:
                                                                            в расте всё еще нет ни ленивых трейтов, ни тонких оберток (без кучи бойлерплейта), а у вас аргументация в его пользу свелась к наличию этих функций в хаскеле.
                                                                            Нет, не сразу (то есть, не в момент написания функции), а в момент её вызова. Тут концепты вообще ничем не отличаются от SFINAE.
                                                                            кажется, это решаемо, по крайней мере на уровне тулинга, стоит поисследовать.
                                                                              0
                                                                              в расте всё еще нет ни ленивых трейтов

                                                                              Макросы


                                                                              … ни тонких оберток (без кучи бойлерплейта)

                                                                              struct Wrapper(liba::Type);
                                                                              
                                                                              impl libb::Trait for Wrapper {
                                                                                  // trait items
                                                                              }

                                                                              Как по мне, так бойлерплейт минимален.

                                                                                0
                                                                                Макросы
                                                                                можете привести пример?
                                                                                Как по мне, так бойлерплейт минимален.
                                                                                бойлерплейт там, где у вас // trait items
                                                                                  +1
                                                                                  бойлерплейт там, где у вас // trait items

                                                                                  Почему бойлерплейт? Это же и будет реализацией трейта.

                                                                                    0
                                                                                    Почему бойлерплейт? Это же и будет реализацией трейта.
                                                                                    да, остается всего ничего — реализовать трейты liba::Type для Wrapper.
                                                                                      +1

                                                                                      Я не понимаю, в чём у вас претензия. Если бы Rust не был так строг к глобальной когерентности трейтов, эту реализацию всё равно пришлось бы писать.

                                                                                        0

                                                                                        …что и было вашей изначальной целью, разве нет?

                                                                                          0
                                                                                          …что и было вашей изначальной целью, разве нет?
                                                                                          речь шла про трейты относящиеся к liba::Type, а не libB::Trait. Оказывается, их можно просто derive'нуть, как объяснил PsyHaSTe ниже. Признаю, я был неправ.
                                                                                  0
                                                                                  либо я вас не понял, либо это делается оберткой над числом

                                                                                  Если вкратце, то нет, этого недостаточно. Вы можете из обёртки убрать код, который проверяет числа на простоту, и ничего в программе от этого не сломается. Проверка там — это всего лишь действие, без всякого свидетеля этого действия


                                                                                  в расте всё еще нет ни ленивых трейтов, ни тонких оберток (без кучи бойлерплейта)

                                                                                  Погуглил rust newtype — есть это. По идее, с растоманской системой макросов можно и бойлерплейт по наследованию трейтов от оборачиваемого типа скрыть.


                                                                                  кажется, это решаемо, по крайней мере на уровне тулинга, стоит поисследовать.

                                                                                  Я не встречал ни одну тулзу, которая бы это делала.


                                                                                  К слову, забавно, что это было в оригинальном пропозале на концепты из нулевых, но потом это выпилили.

                                                                                    0
                                                                                    Проверка там — это всего лишь действие, без всякого свидетеля этого действия
                                                                                    проверка, при завале которой ничего не происходит? Я совсем теряюсь в ваших формулировках…
                                                                                    Погуглил rust newtype — есть это
                                                                                    если я правильно понял, это просто альтернативный синтаксис над композицией
                                                                                    По идее, с растоманской системой макросов можно и бойлерплейт по наследованию трейтов от оборачиваемого типа скрыть.
                                                                                    если ничего не поменялось, попытки так сделать не убирают бойлерплейт, а всего лишь его усложняют
                                                                                    Я не встречал ни одну тулзу, которая бы это делала.
                                                                                    например, последняя студия берет методы для автодополнения из концептов. В принципе, реально проверять метод на то, что дергаются только указанные в концепте методы. Единственное что проверенный в концепте метод может быть не лучшей перегрузкой для конкретного T, например концепт проверяет наличие T::foo(int), а на деле шаблонная функция вызовет T::foo(float); или что-нибудь в таком духе.
                                                                                      0
                                                                                      если ничего не поменялось, попытки так сделать не убирают бойлерплейт, а всего лишь его усложняют


                                                                                      Вроде бы, если это нужно делать для фиксированного libb::Trait, то можно собственный Derived написать.
                                                                                      Вот примерно так: doc.rust-lang.org/reference/procedural-macros.html#derive-macros
                                                                                        0
                                                                                        Вот примерно так: doc.rust-lang.org/reference/procedural-macros.html#derive-macros
                                                                                        там тоже начинаются приколы. Например, трейт не может требовать или обращаться к полю структуры, а значит для полей надо как минимум писать геттеры и прописывать их в трейты (для mut и const отдельно). Всего, к сожалению, я попросту не помню, но кода получается многовато и окупается только для очень глубоких иерархий больших объектов, что в общем-то является антипаттерном и в тех ЯП, где наследование есть.
                                                                                          0

                                                                                          Все он может. Работают ведь как-то serde derive. Ну например:


                                                                                          struct Foo(i32);
                                                                                          #[derive(Debug)]
                                                                                          struct Bar(i32);
                                                                                          
                                                                                          fn main() {
                                                                                              // println!("{:?}", Foo(10));
                                                                                              println!("{:?}", Bar(20));
                                                                                          }

                                                                                          Дебаг реализован для внутреннего типа, поэтому реализация для ньютайпа компилятором спокойно может быть сгенерирована

                                                                                        0
                                                                                        проверка, при завале которой ничего не происходит? Я совсем теряюсь в ваших формулировках…

                                                                                        При отсутствии которой ничего не происходит. Если упрощать, вы можете удалить из вашего класса проверку на простоту, и вся остальная программа продолжит компилироваться.


                                                                                        если я правильно понял, это просто альтернативный синтаксис над композицией
                                                                                        если ничего не поменялось, попытки так сделать не убирают бойлерплейт, а всего лишь его усложняют

                                                                                        Я чтением rust by example и ограничился в своём познании раста, это надо более продвинутых растоманов спрашивать.


                                                                                        например, последняя студия берет методы для автодополнения из концептов. В принципе, реально проверять метод на то, что дергаются только указанные в концепте методы.

                                                                                        Я что-то чувствую, что тут где-то тьюринг-полнота вылезет. Впрочем, даже если бы не вылезла, то сегодня таких инструментов нет.

                                                                                          0
                                                                                          Я что-то чувствую, что тут где-то тьюринг-полнота вылезет
                                                                                          можно проверить достаточность, но нельзя проверить противоречия, пример. Либо делать очень педантичные проверки — в том числе запретить все неявные преобразования типов и вызовы конструкторов, даже rvalue->lvalue.
                                                              0

                                                              То есть "шланг" это тоже "компилятор" в кавычках?

                                                          0
                                                          Смею предположить. Похоже тут всё на память замешано. Отсюда и оптимизации сразу на 128 бит. 128-бит куски читаются/пишутся также быстро как инты и лонги. А если на это дело набросить векторы «из коробки», ну т.е. компилятор сам всё должен разруливать с векторами на эти 128 бит, то действительно может получиться афигеть какой не слабый профит.
                                                          +3
                                                          128 битные числа — то не проблема. Числа с плавающей точкой — вот проблема. Ятро традиционно не пользуется ими, поэтому так же традиционно не сохраняет/восстанавливает регистры FPU при переключении контекста.
                                                            0

                                                            Это интересный вопрос. Получается, надо делать отдельную платформу для ядра. Без (i/u)128, без f* любого размера. Наверное, ещё без нескольких других штук.

                                                              +2
                                                              То есть, вся проблема в том, что кто-то в rs-коде может случайно задействовать float-переменную? Но и в C тоже может, там это как решается?
                                                                +2

                                                                Я не до конца эту часть понимаю, но, видимо, проблема в том, что при вызове функции компилятор сохраняет на стек и float-регистры (вместо обычного ядерного "только int'овые регистры).
                                                                Вот как проблему решают в С — вопрос интересный.


                                                                Нашёл, вот: https://www.ucw.cz/~hubicka/papers/abi/node33.html

                                                                  +4
                                                                  Я так понял, речь о том, что если мы проваливаемся в ядро по прерыванию от любого устройства или таймера, то при продолжении выполнения user-кода нужно всё восстановить так, как будто прерывания и не было. Если регистры общего назначения явно сохраняются и восстанавливаются, то всякие FPU, SSE и т.п. — под вопросом. Если код ядра их испортит, а при выходе из обработчика прерывания мы их не восстановим, программа продолжит работать некорректно. Если же всегда сохранять и восстанавливать всё, что есть в CPU — теряем эффективность.
                                                                    +1

                                                                    Именно. Более того, насколько я понимаю, во время syscall'ов тоже не сохраняют (для ускорения).

                                                                      +2

                                                                      Вообще эти вещи можно попытаться разруливать на уровне custom calling convention. В расте у же есть окружение no_std, которое используется эмбеддерами. Не вижу принципиальных проблем сделать обрезанную версию библиотеки core, которая не содержала бы f32/f64. Плюс что-то вроде extern "linux" для FFI который бы отвечал протоколу ядра. В конце концов, можно использовать soft float и тем самым на уровне компилятора исключить использование FPU регистров для float-ов. Другое дело что LLVM может лезть в регистры SSE.


                                                                      То что базовая версия языка гарантирует наличие определенных типов данных не значит, что нельзя сделать урезанное окружение с особыми ограничениями.


                                                                      Короче проблема по мне выглядит скорее административной чем технической.

                                                                      +3
                                                                      По моему это не ответ «как проблему решают в С».
                                                                      А ответ, я полагаю, это параметры для gcc вида
                                                                      -mno-sse -mno-mmx -mno-80387
                                                                      есть ещё -mgeneral-regs-only
                                                                      Если правильно помню, при таких параметрах код с float/double просто не будет компилироваться.
                                                                  0

                                                                  В данном случае столкнулись интересы двух групп, склонных к долгой неторопливой адаптации. Так что, боюсь, она будет ну очень долгой и закончится вместе с отмиранием всех архитектур, не умеющих 128 бит.

                                                                    +5

                                                                    Насколько я понимаю, 32-бита в линуксе умирать не планирует из-за обилия embedded. i386 умер (уже?), а вот всякие 32-битные армы цветут и пахнут и умирать не собираются.


                                                                    Пока любой микроволновке достаточно 2Гб оперативной памяти, 32-бита будет с нами.

                                                                      +5
                                                                      > Пока любой микроволновке достаточно 2Гб оперативной памяти, 32-бита будет с нами.

                                                                      как хорошо, когда микроволновке нужно было десяток байт памяти для часов и настроек ))
                                                                        +6

                                                                        Да что ж в этом хорошего? Ни в дум поиграть, ни включить через интернет /s

                                                                          +1

                                                                          Ещё лучше, когда микроволновке достаточно пружинки с противовесом для настроек и скользящего контакта для их использования :-)

                                                                            0

                                                                            и кукушки для сообщения о завершении цикла. А циферблат и стрелки — это для продвинутых версий...)))

                                                                              +1

                                                                              Перепутал в общем-то, там моторчик стоит, запараллеленный со шпинделем стола.


                                                                              Но в принципе да, в какой-то момент переезда пришлось поменять микроволновку с "цифровой" на обычную с двумя ручками и полностью механической логикой — какой-то прям разницы, кроме внешнего вида, не заметил.

                                                                    +6
                                                                    Я не хейтер, но забавно видеть
                                                                    потенциальная возможность “паники ядра”
                                                                    и
                                                                    Rust дает возможность снизить риск появления уязвимостей, которые вызваны ошибками при работе с памятью, включая обращение к области памяти после ее освобождения и выход за границы буфера.
                                                                    в одной статье.
                                                                      +8
                                                                      А я не фанат и вообще на Rust не писал, но противоречий не вижу: «снизить риск появления уязвимостей» != «исключить вообще все уязвимости», не говоря уже о том, что непонятно, является ли паника уязвимостью в данном контексте.
                                                                        +1
                                                                        предотвращать ООМ в си пожалуй даже проще, т.к. все аллокации явные.
                                                                        не говоря уже о том, что непонятно, является ли паника уязвимостью в данном контексте.
                                                                        В контексте ядра паника точно и всегда является уязвимостью. Раст спроектирован больше под юзерспейс, где ошибки аллокации обрабатывать не принято (зачем пытаться обрабатывать OOM если по завершению памяти тебя всё равно прибьет OOM killer?), соответственно и АПИ спроектирован так, словно ООМ не бывает.
                                                                          +4

                                                                          В расте тоже все аллокации явные. Плюс недавно завезли интерфейс кастомных аллокаторов, который позволяет грамотно обрабатывать ситуацию нехватки памяти.


                                                                          Раст спроектирован больше под юзерспейс, где ошибки аллокации обрабатывать не принято

                                                                          Шта? Идеология раста как раз-таки направлена против замалчивания ошибок. А Result<T, E> вместе с оператором ? позволяет аккуратно пробрасывать ошибки вверх по коду. Не вижу причин, почему нельзя было бы распространить обработку ошибок еще и на ситуации с отказом аллокатора.

                                                                            +9
                                                                            Шта? Идеология раста как раз-таки направлена против замалчивания ошибок.

                                                                            Идеология-то направлена на это, но недостаток памяти долгое время считался — да и, пожалуй, считается — большинством библиотек ошибкой, которой нужно избегать, а не отказом, который обязательно нужно обрабатывать, уметь делать это удобно и в любом месте.


                                                                            В языке нет технических причин не пробрасывать отказ аллокатора в виде Result. Но интерфейсов, которые так делают, исчезающе мало. Они только начинают робко появляться в стандартной библиотеке, а потом ещё потребуется достаточно много времени, чтобы они распространились по экосистеме.


                                                                            Дело же не ограничивается только контейнерами стандартной библиотеки: просто теперь почти каждый метод something() -> T будет иметь своего брата try_something() -> Result<T, std::alloc::Error>. Муторно, но core team справится.


                                                                            А теперь надо будет переучить всю остальную экосистему, что по-хорошему вместо items.push() надо писать items.try_push()? И придумать себе какой-то тип ошибок, чтобы возвращать его. И так в каждой библиотеке.


                                                                            Оно и понятно. Никто не хочет плодить везде некрасивые интерфейсы, чтобы иметь возможность обрабатывать ситуации, которые большинство людей не знает, как обрабатывать, и не особо переживает по этому поводу. Закончилась память? Ну и что, ОС прибьёт процесс, система продолжит работу, а ты в следующий раз пиши программу лучше или ставь больше оперативной памяти. Для пользовательского кода — это не критическая ситуация. Но у ядра ОС немного другая позиция, где не так допустимо в любой непонятной ситуации поднять лапки и перезагрузиться. Я напомню, что Linux — это монолитное ядро, так что драйверы не очень тщательно изолированы друг от друга и от «ядра ядра».

                                                                              0

                                                                              Да, пожалуй. Тут добавить нечего. Разве что проблемы, как мне кажется, возникают оттого, что попытка обработать такую ситуацию как правило сама может привести к нехватке памяти. То есть код обработки ошибок становится сложнее и хрупче основной бизнес логики. А double fault это мрак, что в ядре что в программах.

                                                                                +1

                                                                                Вся необходимая память для обработки ошибок включая оом выделяется статически на старте очень заранее от собственно оом, что бы не попадать в ситуацию двойного отказа, ну и если у вас оом в момент инита ядра то тут уже все, ничего не поделать тут и ядро паникует.

                                                                                  0

                                                                                  Да, это я знаю. Я говорил об обработке в обычных приложениях, где такое обычно не делают.

                                                                                0

                                                                                Зачем переписывать стандартные интерфейсы, если в ядре Линукса будет использоваться no_std?

                                                                                  +3

                                                                                  stdlib — незачем. Но говорим Rust — подразумеваем экосистему. Когда кто-то захочет притащить в ядро какую-нибудь библиотеку, она должна быть написана по канонам. А всё, что не написано — нужно адаптировать и переписывать, а не тащить что попало из crates.io.


                                                                                  Фактически, Rust-for-Linux можно считать отдельным языком и отдельной экосистемой, подмножеством Rust-at-large, которое вырастет из Rust-for-embedded. Там везде #[no_std] и понапридумывают атрибутов вроде panics, sleeps, и так далее.

                                                                                0
                                                                                В расте тоже все аллокации явные.… Шта? Идеология раста как раз-таки направлена против замалчивания ошибок
                                                                                Я больше например про вот такие методы — тут и неявная аллокация, и подавленная ошибка, и спрятанная паника (хоть и с невероятным условием).

                                                                                Можно было бы сделать какой-нибудь режим сборки с педантичной проверкой аллокаций, в котором нельзя игнорировать ошибки выделения памяти и методы подобные выше будут попросту запрещены. Правда, на выходе получится по сути другой параллельный ЯП.
                                                                            +4

                                                                            Паника — это фича Rust'а. Паника — это безопасный метод завершить unsound код. Альтернативой будет "продолжать работать", что означает, что какие-то куски кода что-то куда-то пишут мимо, а потом с этим "мимо" делают сайд-эффекты куда-то.

                                                                              +10
                                                                              Претензия Линуса в том, что вместо возвращения ENOMEM в том или ином виде неявный аллокатор вызовет панику. Никакого «писать что-то мимо» здесь нет.

                                                                              Upd.: грубо говоря, если какой-то подсистеме не хватило памяти, то это еще не повод для вызова общей паники. Пусть лучше отвалится юзерспейсный клиент, который вызвал такой драйвер, которому не хватило памяти.
                                                                                0

                                                                                Это абсолютно правильная критика. Но, вообще говоря, для поддержки embedded в Rust давно есть возможность использовать свой аллокатор и не использовать std (полагающийся на наличие всегда доступного аллокатора, который вывернется из типоситуации с помощью panic).

                                                                                  0
                                                                                  Тогда все претензии к разработчикам патча :-) Как и Линус, я не против Раста в ядре :-)
                                                                                    +3

                                                                                    Трейт GlobalAlloc находится в alloc, причем там же лежат паникующие реализации стандартных коллекций. Получается, чтобы убрать плохие коллекции, нужно делать свой alloc. А это может оказаться совсем не просто, поскольку Rust не очень здорово поддерживает такие вещи (вспоминается xargo)

                                                                                0

                                                                                А в чем забавность? Раст паникует и роняет ядро, а си портит вам память и продолжает дальше работать некорректно. Во многих случаях подключивающий драйвер звука будет предпочтительнее умирющей системы. Но не всегда.


                                                                                В любом случае — не взаимоисключающие вещи.

                                                                                +20

                                                                                Моё мнение.


                                                                                У меня есть много претензий к Rust, но я не об этом.


                                                                                Нет ни одной проблемы написать модуль ядра на Rust, как и на С++, да даже на JS.


                                                                                Чтобы внедрить Rust в ядро нужно одноврменно:


                                                                                1. Найти фанатиков Rust ( в хорошем смысле этого слова), которые умеют в него, понимают его концепции.
                                                                                2. И которые умеют в разработку ОС, и все низкоуровневые вещи.
                                                                                3. И которые знают и понимают Linux ядро.

                                                                                Т.е. люди должны пройти пятилетку за три месяца. Переосмыслить знания полученные за много лет в области проектирования ОС и перенести это на Rust.


                                                                                Тут звёзды не сходятся.


                                                                                А пока я вижу, что сам Торвальдс:


                                                                                Not being a Rust person, I can only guess based on random pattern
                                                                                matching, but this looks like these "panicking intrinsics" panic at
                                                                                run-time (by calling BUG()).

                                                                                Банальный вопрос. Кто будет рассматривать патчи написанные на Rust? Сишники?


                                                                                Я всеми руками и ногами выступаю за то, чтобы начать ядро писать на более безопасном языке. И пусть это будет Rust.


                                                                                Но видя предложенные патчи и критику Линуса, я просто понимаю, что люди толкающие Rust вообще ничего не понимают в разработке ядра.


                                                                                И это, это печально.

                                                                                  +4
                                                                                  Не думаю, что разработчики Android так уж «ничего не понимают в разработке ядра».

                                                                                  И дело тут не в фанатизме к Rust. Они в своём блоге пишут, что CVE-репорты, связанные с кодом ядра, отражаются и на Андроиде (звучит логично). Поэтому они ищут способы, как свести эти уязвимости к минимуму. Ничего личного, просто бизнес.

                                                                                  Давайте не будем слишком строги к самым первым шагам по интеграции Rust в ядро. Как сказал известный рэпер, «историческая х… ня» происходит сейчас прямо на наших глазах.

                                                                                  Время покажет.
                                                                                    +3

                                                                                    Для сишника-разработчика линукса со стажем в расте точно нет ничего сложного. Если питонисты/джависты за полгода осваивают, то махровые сишники с волосатыми ручищами за один день должны влетать элементарно. По крайней мере от хороших плюсовиков знакомых у меня как раз такие впечатления.


                                                                                    Так что да — смотреть будут сишники, которым интересен раст. Ну или в обратную сторону — растовики будут писать код, а Линус их поправлять "у нас в ядерной разработке так не принято", и они будут расти из раста в область написания ОСей.


                                                                                    Ничего печального, просто Москва не сразу строилась, всё вместе и сразу не бывает

                                                                                    +5
                                                                                    Можно просто встроить в ядро питон, написанный на Rust и писать на питоне
                                                                                    github.com/RustPython/RustPython
                                                                                    [sarcasm mode off]
                                                                                      0
                                                                                      В ядре и так уже есть минимум два интерпретатора — байткод ACPI (ASL) и eBPF.
                                                                                      +1
                                                                                      Главной проблемой, по его мнению, является потенциальная возможность “паники ядра” в некоторых ситуациях. Это может быть нехватка памяти, когда операции динамического распределения памяти могут завершаться ошибкой. Торвальдс заявил, что такой подход в ядре принципиально недопустим.

                                                                                      Он говорит не о панике ядра, а о панике Раста. Это поведение функций, выделяющих память на куче в юзерспейсе, в стандартной библиотеке.


                                                                                      При этом довольно очевидно, что стандартная библиотека Раста в ядре и не нужна. Так было бы и с любым другим языком. В Linux не используется стандартная библиотека Си и в частности тот же самый malloc. Там свои специализированные аллокаторы.

                                                                                        0

                                                                                        Речь о том что паника раста будет приводить к панике ядра. Что в целом корректное предположение. Насколько я помню растом не гарантируется что панику всегда можно поймать. Если в конечном приложении это ещё можно рулить panic=unwind то в библиотечных частях — уже не очень. Да и ловить надо там где проблема возникла а не где-то выше.


                                                                                        так что полагаю будут оборачивать все core-функции в try_xxx. Мб заодно стабилизируют наконец never-type чтобы не дублировать апишки.


                                                                                        При этом довольно очевидно, что стандартная библиотека Раста в ядре и не нужна.

                                                                                        Так речь не про стд, а про кор (ну или по крайней мере alloc), тот же вектор паникует на оом, но является частью alloc::vec, а не стд. Но общий посыл да — что под линукс видимо придется половины экосистемы переписывать. Свой аллок, свой серде, своё всё.

                                                                                          0

                                                                                          Никто и не предполагал, что в ядре будет "обычный Раст". В Redox тоже "не обычный". И в Fuchsia.


                                                                                          Какой serde в ядре?..

                                                                                            0
                                                                                            Какой serde в ядре?..

                                                                                            Обычный, у него no_std фича есть. Причем он мне понадобился когда я свою VM писал, по уровню абстракции сравнимо с ОС.

                                                                                              0

                                                                                              Я думаю, что консервативность кодовой базы линукса недооценена. Сомневаюсь, что в mainline возьмут.

                                                                                        –3
                                                                                        В прошлом году разработчики ядра Linux предложили использовать Rust для нового встроенного кода


                                                                                        Хорошо что кроме Линукса есть ещё BSD системы, а так же потихоньку, но планомерно развивающаяся Haiku-OS. Надеюсь туда растофаны не доберутся.
                                                                                          +1
                                                                                          Да, хорошо. Например, в NetBSD можно сразу на Lua писать.

                                                                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                        Самое читаемое