Как стать автором
Обновить

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

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

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

Беспокоиться нужно будет тогда, когда любую «замечательную фичу» сообщество будет воспринимать розовыми соплями.
НЛО прилетело и опубликовало эту надпись здесь
признание Rust в качестве списка языка разработки Android

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

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

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

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

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


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

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


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


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

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

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

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

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 приняли решение включить его в стандарт языка.

НЛО прилетело и опубликовало эту надпись здесь
Видимо в rust очень много накручено вокруг корректности многопоточности при отсутствии блокировок.

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

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


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

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

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


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


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


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


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

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

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

Но в ядре Linux нельзя пользоваться SSE-регистрами, потому что это дорого: если ядро будет трогать их значения, то их надо сохранять-восстанавливать при переключении контекста, чтобы не ломать пользовательские процессы. Дешевле ядру просто не пользоваться и не трогать.
Значит это плохое ядро. Всего навсего.
Возможно. Напишите своё, хорошее, на чистом Расте. Тут же вопрос, можно ли Раст, с его SSE-манипуляциями, пускать в «плохое» ядро.
НЛО прилетело и опубликовало эту надпись здесь
А префикс LOCK может применяться к store-инструкциям SSE?
Тогда atomic-операции с u128 могут выражаться через него.
НЛО прилетело и опубликовало эту надпись здесь
хотя интересно, что делают опции вроде AVX2-accelerated foobar crypto algorithm в моём make menuconfig

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


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


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

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


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

НЛО прилетело и опубликовало эту надпись здесь

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


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


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


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

НЛО прилетело и опубликовало эту надпись здесь

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


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

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

А что в нем не валидного? Вот я писал свою библиотеку на С++, для своих самописанных 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 сделать также?

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

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

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

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


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

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


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


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

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


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

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


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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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


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


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


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


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


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


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

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

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

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


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

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

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

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


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

Я не знаю как 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 ценой производительности).

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

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

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


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


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

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


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

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

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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


template<typename T, typename = decltype(declval<T>() + declval<T>())>
T foo(T a, T b)
{
  return a + b;
}
НЛО прилетело и опубликовало эту надпись здесь
Я хочу как можно раньше знать

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


Что тип 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;
}
НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
эмм… я скорее ожидал что саркастический тон будет очевиден, но видимо ошибся. У меня есть предположение что это за чел, не так уж много хаскелистов на плюсовых конференциях, но я его акк на habre не знаю. Но если я угадал, то он сам тот ещё «троль», и не думаю что мой вопрос был воспринят всерьёз.
примется тайпчекером независимо от того, что выражены не все условия на operator+ (например, он должен возвращать тип, неявно конвертируемый в T, что здесь не написано).
Во-первых, вы могли написать как-то так и все ваши требования будут выполнены. Во-вторых, получается очередной спор на тему что лучше — трейты или концепты, где фундаментальная разница в том, что у концептов шире функционал (за счет «ленивости» проверок можно работать с типами, которые не знают о соответствующих концептах, а трейты лучше тем, что требования для типа задаются явно… однако с концептами такая явность достигается простейшим:
static_assert(MyConcept<MyTypeToCheck>);
Так что как раз с точки зрения проверок типов с++ мощнее. Вот чего не хватает по сравнению с rust — возможности всяких #[derive(Serialize, Deserialize)], что приедет с рефлексией. Собственно, предлагаю вам в ваших дальнейших спорах «rust vs c++» использовать именно этот аргумент
НЛО прилетело и опубликовало эту надпись здесь
Конечно, мог бы. Но я не обязан так делать, и в этом проблема.
вы всегда можете требовать от с++ кода любой степени привередливости по вашему усмотрению. А вот от раста большей выразительности уже нет.
Ленивость проверок вообще никак не связана с подобной расшрияемостью. В хаскеле вон проверки не ленивые, но тайпклассы никто не мешает писать пост-фактум для типов, которые о них ничего не знают.
а если вы не можете управлять ни тайпклассами, ни типами? Ну то есть пытаетесь засунуть сущность из внешней библиотеки A в метод другой внешней библиотеки B?
…которое снова проверяется только тогда, когда функция инстанциируется для конкретного типа.
не функция, а концепт, и проверяется сразу. Соответственно любой метод, принимающий тип, удовлетворяющий MyConcept, примет тип MyTypeToCheck.
В том же смысле, в котором питон с точки зрения проверок типов мощнее C++.
питон в принципе не способен на проверки времени компиляции сложнее удовлетворения синтксису, так что этот ваш аргумент попросту абсурден.
НЛО прилетело и опубликовало эту надпись здесь
Во-вторых, я бы посмотрел на код, читающий число из файла, и передающий это число в другую функцию, которая статически требует наличия проверок на то, что число является простым (это кивок в сторону завтипов, если что).
либо я вас не понял, либо это делается оберткой над числом
Но даже если бы такой возможности не было, я бы сделал newtype-обёртку (не имеющую никакой стоимости в рантайме) и написал бы все нужные инстансы для неё:
в расте всё еще нет ни ленивых трейтов, ни тонких оберток (без кучи бойлерплейта), а у вас аргументация в его пользу свелась к наличию этих функций в хаскеле.
Нет, не сразу (то есть, не в момент написания функции), а в момент её вызова. Тут концепты вообще ничем не отличаются от SFINAE.
кажется, это решаемо, по крайней мере на уровне тулинга, стоит поисследовать.
в расте всё еще нет ни ленивых трейтов

Макросы


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

struct Wrapper(liba::Type);

impl libb::Trait for Wrapper {
    // trait items
}

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

Макросы
можете привести пример?
Как по мне, так бойлерплейт минимален.
бойлерплейт там, где у вас // trait items
НЛО прилетело и опубликовало эту надпись здесь
Почему бойлерплейт? Это же и будет реализацией трейта.
да, остается всего ничего — реализовать трейты liba::Type для Wrapper.

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

НЛО прилетело и опубликовало эту надпись здесь
…что и было вашей изначальной целью, разве нет?
речь шла про трейты относящиеся к liba::Type, а не libB::Trait. Оказывается, их можно просто derive'нуть, как объяснил PsyHaSTe ниже. Признаю, я был неправ.
НЛО прилетело и опубликовало эту надпись здесь
Проверка там — это всего лишь действие, без всякого свидетеля этого действия
проверка, при завале которой ничего не происходит? Я совсем теряюсь в ваших формулировках…
Погуглил rust newtype — есть это
если я правильно понял, это просто альтернативный синтаксис над композицией
По идее, с растоманской системой макросов можно и бойлерплейт по наследованию трейтов от оборачиваемого типа скрыть.
если ничего не поменялось, попытки так сделать не убирают бойлерплейт, а всего лишь его усложняют
Я не встречал ни одну тулзу, которая бы это делала.
например, последняя студия берет методы для автодополнения из концептов. В принципе, реально проверять метод на то, что дергаются только указанные в концепте методы. Единственное что проверенный в концепте метод может быть не лучшей перегрузкой для конкретного T, например концепт проверяет наличие T::foo(int), а на деле шаблонная функция вызовет T::foo(float); или что-нибудь в таком духе.
если ничего не поменялось, попытки так сделать не убирают бойлерплейт, а всего лишь его усложняют


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

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


struct Foo(i32);
#[derive(Debug)]
struct Bar(i32);

fn main() {
    // println!("{:?}", Foo(10));
    println!("{:?}", Bar(20));
}

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

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

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

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

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

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

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


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

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

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

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


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


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

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

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


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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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


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

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

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


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

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

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

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


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


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


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


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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

Моё мнение.


У меня есть много претензий к 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 вообще ничего не понимают в разработке ядра.


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

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

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

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

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

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


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


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

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

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


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

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


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


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

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

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


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

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

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

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

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


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