openlang

unsafe место на блоке extern, а не в точке вызова

Rust 2024 требует unsafe extern "C" { ... } вместо голого блока extern, и это самое честное небольшое изменение синтаксиса в языке за последние годы. Объявление внешней функции всегда было непроверенным утверждением о её сигнатуре. Ошибитесь в типах аргументов, и каждый вызов будет неопределённым поведением, как бы тщательно ни проверяли место вызова. Старый синтаксис ставил unsafe только в местах вызова и отправлял рецензентов смотреть не на те строки.

Теперь внутри блока элемент можно пометить safe fn, если у внешней функции действительно нет предусловий, и тогда вызывающему коду unsafe вообще не нужен. Вся ответственность переходит на одно объявление, и именно там ей место на ревью.

Та же редакция делает unsafe_op_in_unsafe_fn предупреждением по умолчанию, так что тело unsafe fn больше не считается одним большим небезопасным блоком. Каждой операции нужен свой unsafe { } и, по соглашению, комментарий SAFETY с объяснением, почему всё корректно.

Следующим шагом должно стать обязательное обоснование для каждого safe fn во внешнем блоке. С этим справился бы линт.

Разговор продолжается 4

Следуя доводам
  1. Ссылка на ответ

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

  2. Ссылка на ответ

    Сигнатура это половина дела. Вторая половина это раскрутка стека. Паника, пытающаяся выйти из Rust-функции extern "C", теперь прерывает процесс, а исключение C++, переходящее в Rust, определено только через ABI "C-unwind". Значит, объявление должно ещё и говорить, может ли через него что-то раскручиваться. Какой уже созданный объект владеет дескриптором в этот момент? При аварийном завершении ни один деструктор не выполнится.

  3. Ссылка на ответ

    У этого есть почти близнец в проверке Sendable в Swift. Заголовки C ничего не говорят о том, какой поток что может трогать, поэтому обёртку над C-дескриптором обычно помечают @unchecked Sendable, и компилятор верит автору на слово, почти как с safe fn во внешнем блоке. Такие пометки лучше держать редкими и легко находимыми через grep, с комментарием о том, какая блокировка или очередь делает это правдой. А @preconcurrency import тихо ослабляет диагностику Sendable для модуля, что нормально во время миграции и легко забывается потом.

  4. Ссылка на ответ

    Не совсем одно и то же. @unchecked Sendable ручается за тип везде, куда он попадёт, а safe fn ручается за предусловия одной внешней функции. И то и другое должно легко находиться через grep, и Rust хотя бы заставляет написать слово unsafe на блоке, где лежит это поручительство, так что один поиск находит все такие утверждения.

Другие стороны того же вопроса

  1. age>=18=>allow и ридер Racket

    Авторы правил хотят писать age>=18=>allow, без пробелов и скобок. На вид изменение крошечное. Но на каком этапе решается, что…