Обновить

Твой код на Rust компилируется, проходит тесты и является UB. Ты просто об этом не знаешь

Уровень сложностиСредний
Время на прочтение6 мин
Охват и читатели12K
Всего голосов 19: ↑18 и ↓1+22
Комментарии7

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

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

Но библиотеку я опубликовал, ей даже кто-то пользуется, надо проверить, решил я.

test epmd::server::tests::test_handle_list_nodes ... error: unsupported operation: `clock_gettime` with `REALTIME` clocks not available when isolation is enabled
   --> /home/am/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/pal/unix/time.rs:107:22
    |
107 |         cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap();
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
    |
    = help: set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;

Ну как так-то? Нет, MIRIFLAGS=“-Zmiri-disable-isolation” cargo +nightly miri test прошёл, UB у меня, вроде, нет. Но что-то не так в стандартной библиотеке, в коде, который отвечает за операции со временем (!). Серьёзно?

Всё с ним норм, явно же написано что unsupported operation - miri не может проверить системный вызов времени. Miri с выключенным isolation требует детерминизма в коде.

И я не совсем понимаю почему вы используете SystemTime::now(), а не Instant::now() для отсчёта временных интервалов. Но скорее всего последний поддерживается в Miri.

И не пытайся восстановить указатель из числа через as, если у тебя нет провенанса под рукой, для этого с недавних пор есть with_exposed_provenance, и это не косметика, а единственный задокументированный способ сделать такое без UB.

Тем временем документация на with_exposed_provenance:

This is fully equivalent to addr as *const T
https://github.com/rust-lang/rust/blob/main/library/core/src/ptr/mod.rs#L970

и эти правила строже, чем в C

Вспоминая разумный отказ от TBAA - где-то строже, где-то слабее.

Про UB ещё можно рассуждать, что мы в конечном счёте имеем дело с LLVM, а тот эксплуатирует UB как обычно. Когда LLVM выкидывал пустые бесконечные циклы - это отразилось на всех фронтендах, на C, C++, Rust - хотя с точки зрения Си и раста он на это права не имел.

Я нарывался в Rust на UB при операциях с числами с плавающей запятой из-за оптимизации LLVM. Одно и то же выражение на ПК оказывалось не нулевым, а на МК - нулевым. Просто из-за перестановки местами множителей при оптимизации. При сохранении порядка умножений, как в исходном выражении, всё было хорошо. А при оптимизации при промежуточном умножении возникало исчезновение порядка и результат оказывался нулевым.

Увы, вообще не знаю Rust, но почему-то статья зацепила. Пишу на С и C++. UB при работе с указателями представляется мне дьявольскими кознями, которые придумали, чтобы осложнять мне жизнь. Конечно, перестановка команд и прочие оптимизации до некоторой степени ускоряют работу программ (иногда). Но обычно хочется, чтобы код, который я написал, работал именно так, как я его написал. К примеру, написал "++" - увидел в ассемблере inc в этом месте. Написал пустой цикл - и в этом месте будет задержка, причем ее можно точно посчитать в тактах.

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

С некоторых пор я перестал понимать, как делать это корректно без UB. Кажется, те, кто работает над стандартами C++, не думают о потребностях людей, занимающихся низкоуровневым программированием. А ведь при создании и С и С++ позиционировались как замена ассемблера, и позволяли точно задать, что будет делать программа.

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

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

Интересно, а в Rust есть что-то, позволяющее корректно реализовывать низкоуровневые операции?

Иногда странно наблюдать, как С/С++ пытаются заменить на Rust, тогда как С и С++ позиционировались именно как замена ассемблера со всеми его возможностями, а у Rust не было изначально, и нет до сих пор, единой теории, что же такое “безопасное управление памятью” и как это должно быть реализовано в компиляторе.

самое ближайшее где оно реально нужно, это когда мы соприкасаемся с С/С++ апи, например, если нужны обёртки

CreateVertexArrays: unsafe extern "C" fn(i32, *mut u32),

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

я пока лутаю удобство от концепции Раста на поверхности и кайфую без уб на своей тачке, )

ну, если так нужен С/С++ в конце концов есть соседний Зиг и там что-то удобно и что-то тоже прикольно)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации