Какие последствия возникают, если паника Rust пересекает границу функции с ABI C?
Если паника Rust выходит через границу функции с ABI C, которая не разрешает раскрутку стека, Rust аварийно завершает процесс. Паника не должна пересекать такую границу: её нужно перехватить внутри Rust и преобразовать в код ошибки, безопасный результат или другой согласованный сигнал.
FFI нужен для вызова кода между Rust и языками вроде C, где соглашения о вызовах, представлении данных и обработке ошибок отличаются. В традиционном C нет встроенной модели паники Rust, поэтому передача исключительного управления через границу ABI не имеет общего, согласованного механизма.
Rust разделяет ABI, допускающие раскрутку стека, и ABI, которые её не допускают. Это позволяет явно обозначить границу, через которую может проходить unwinding, вместо того чтобы неявно полагаться на совместимость разных механизмов обработки ошибок.
Рассмотрим Rust-функцию, которую вызывает C-код, либо C-функцию обратного вызова, вызванную из Rust. Если внутри Rust возникает паника и она выходит наружу через обычную границу extern "C", обработка считается нарушением условий этой границы.
Неверное решение опасно не только тем, что вызывающая сторона не получит корректный результат. Процесс может быть немедленно прерван, а частично выполненные операции, захваченные ресурсы и состояние внешней библиотеки могут остаться в неожиданном состоянии.
Граница FFI должна иметь явный контракт обработки ошибок. Для ABI C типичный вариант — перехватить панику внутри Rust с помощью catch_unwind и вернуть C-совместимый код ошибки или специальный результат.
В примере паника не покидает функцию с ABI C. В реальном интерфейсе нужно также определить значения кодов ошибок, правила владения ресурсами и допустимое состояние объектов после ошибки; сам catch_unwind не делает произвольные данные автоматически C-совместимыми.
ABI C-unwind предназначен для границ, через которые раскрутка стека действительно разрешена контрактом обеих сторон. Его использование не означает, что C-код автоматически поймёт payload паники Rust или что все ресурсы будут корректно обработаны внешней библиотекой. Выбор ABI должен соответствовать фактическому механизму unwinding и соглашениям используемых языков.
Есть и дополнительное ограничение: catch_unwind предназначен для перехвата паник Rust, реализованных через unwinding. При режиме panic = "abort" перехватить такую панику невозможно — процесс завершается сразу. Кроме того, границу FFI нельзя использовать как способ передачи произвольного исключения между несогласованными средами выполнения.
Rust-библиотека предоставляет C-приложению функцию регистрации обратного вызова. C ожидает, что callback всегда вернёт целое значение ошибки, но Rust-реализация callback может запаниковать из-за внутренней ошибки состояния.
Вариант с прямым вызовом callback через ABI C проще, но при панике приводит к аварийному завершению процесса. Вариант с ABI, разрешающим unwinding, теоретически допускает передачу раскрутки, однако C-приложение может не иметь совместимого механизма обработки и не сможет корректно интерпретировать payload или восстановить свои инварианты.
Выбран вариант с внутренним catch_unwind: библиотека логирует причину, освобождает принадлежащие Rust ресурсы и возвращает заранее согласованный код ошибки. Это сохраняет границу ABI непроницаемой для паники и позволяет C-коду продолжить работу по определённому контракту; цена решения — необходимость явно проектировать восстановление состояния и формат ошибки.
Нет. Такой ABI лишь разрешает unwinding на уровне соглашения о границе; он не превращает payload паники Rust в понятное исключение для C и не гарантирует совместимость с конкретным рантаймом другой стороны. Без общего протокола обработки ошибок безопаснее перехватить панику до выхода из Rust.
catch_unwind гарантированно перехватить панику, возникшую внутри любого вызываемого FFI-кода?Нет. Он предназначен для паник Rust, которые используют механизм unwinding. Иностранное исключение или другой механизм передачи управления не обязан быть совместим с catch_unwind; кроме того, при panic = "abort" паника Rust не раскручивает стек и не перехватывается.
Паника могла возникнуть после частичного изменения глобального состояния, структуры данных или объекта, доступного через FFI. catch_unwind возвращает управление, но не восстанавливает логические инварианты автоматически. Безопасная оболочка должна либо доказать сохранность состояния, либо пометить объект как непригодный, освободить ресурсы по правилам и вернуть ошибку, не позволяя внешнему коду продолжить работу с повреждёнными данными.