Pull to refresh

Comments 9

Отличная статья. Новички в Ржавчине часто не понимают какова роль unsafe в языке и или боятся его как огня и пытаются избегать любыми путями, или наоборот неоправданно активно его используют. В обоих крайностях качество кода сильно страдает — или вместо одного аккуратного сырого указателя получаются монструозные и неуклюжие нагромождения безопасных абстракций, или весь код представляет из себя тотальное нагромождение UB. Даешь больше разъяснительных статей! :)

А что думаете по поводу недавнего поста
(https://manishearth.github.io/blog/2017/12/24/undefined-vs-unsafe-in-rust/?utm_source=newsletter_mailer&utm_medium=email&utm_campaign=weekly)?


… The reason it is still unsafe is because it’s possible to trigger UB by only changing the “safe” caller code. I.e. “changes to code outside unsafe blocks can trigger UB if they include calls to this function”.

Плюс список того, что считается UB (https://doc.rust-lang.org/beta/reference/behavior-considered-undefined.html).


Проблемы, как я понимаю, могу возникнуть в случае использования какого-нибудь чужого крейта. Так например у Tock https://www.tockos.org/blog/2017/crates-are-not-safe/ возникают сложности, т.к. вынуждает их вручную контроллировать зависимости и их изменения.

Статья "Undefined vs Unsafe in Rust" — тоже очень хорошая.


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

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

Да. Стоит заметить, что, например, в коде самого Rust компилятора(!), только 4% кода является unsafe, да и то unsafe там используется в основном для взаимодействия с C библиотеками посредством FFI(Foreign Function Interface).

Позволю себе добавить, что помимо сегфолтов, Rust позволяет статически (на этапе компиляции) доказать отсутствие в программе гонок по данным так же, как он предотвращает обращение к неинициализированной или уже освобожденной памяти без необходимости GC. Причем без каких-либо накладных расходов, то есть производительность от этого не страдает.
А в каких случаях страдает производительность, что вынуждает использовать unsafe? Написано «используйте unsafe для улучшения производительности», и ни примера, ни цифр. У вас случайно нет таких примеров?
Тут сразу стоит сказать что сам по себе `unsafe` не делает ничего особенного и не увеличивает магическим образом производительность на 146% самим фактом своего использования. Он всего лишь позволяет программисту сказать компилятору «я знаю что делаю» и успокоить его анализатор, который в противном случае бы не дал написать потенциально небезопасную конструкцию.

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

Например, если у вас есть вектор элементов то, как уже было сказано в статье, безопасная реализация обязана проверить, что переданный индекс не выходит за пределы массива. Даже если вы храните этот индекс в объекте и твердо уверены, что он верный, при каждом обращении все равно будет выполняться проверка.

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

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

Один из примеров, где такое срезание углов имеет смысл — это реализация итератора по массиву. Доступный размер массива известен заранее, поэтому итератору достаточно хранить у себя текущий индекс. При создании итератора текущий индекс выставляется в 0 и увеличивается на единицу каждый раз, когда пользовательский код зовет `next` и если в массиве есть еще непосещенные элементы. Поскольку этот индекс по определению не может выйти за пределы массива, имеет смысл операцию обращения к элементу делать без проверки индекса (проверка заложена в саму логику итератора), тем самым, повышая производительность, путем убирания лишней проверки, а не всех проверок вообще. Код по-прежнему остается безопасным, просто обеспечение этой безопасности вынесено на другой уровень.

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

Конкретные цифры будут слишком малоинформативны, поскольку зависят от сотни факторов, включая конкретное место использования итератора, оценку компилятором весов при инлайнинге, архитектуру процессора и т.д.
Sign up to leave a comment.

Articles