При вызове FFI-функции с неверно описанным типом параметра почему совпадение размера не делает вызов безопасным?
Совпадение размера параметра не гарантирует совместимость FFI-вызова: важны также ABI, выравнивание, способ передачи значения, допустимые битовые представления и правила владения. Если объявление Rust не соответствует фактической сигнатуре внешней функции, вызов может привести к неопределённому поведению, даже когда оба типа занимают одинаковое число байт.
FFI появился как способ связывать программы и библиотеки, написанные на разных языках, через заранее определённый двоичный контракт. Компилятор Rust не может проверить реализацию внешней функции, поэтому объявление FFI служит описанием уже существующего ABI, а не адаптером, автоматически исправляющим несовместимость типов.
Подход с явным unsafe отделяет проверяемую компилятором часть программы от границы, где корректность зависит от внешнего кода, платформы и документации библиотеки. Это позволяет использовать C-совместимые библиотеки, но переносит доказательство соответствия сигнатуры на разработчика.
Типы могут иметь одинаковый размер, но передаваться по-разному: например, в регистрах или через память, целиком или частями, со специальными правилами расширения и выравнивания. Различаться могут и допустимые значения: набор битов, корректный для одного типа, может быть недопустимым для другого.
Ошибочное объявление способно повредить стек, нарушить соглашение о сохранении регистров, исказить значение параметра или привести к неверному чтению результата. Такое нарушение является проблемой всего вызова, а не только отдельного параметра; локальная проверка адреса или размера не делает его безопасным.
Нужно сопоставить фактическую сигнатуру внешней функции с объявлением Rust по нескольким уровням:
C и Rust ABI не являются взаимозаменяемыми;NULL, длина буфера, изменяемость, время жизни и владение;Одинаковый размер — только одно необходимое наблюдаемое свойство, причём даже оно не всегда достаточно для совместимости. Например, два 32-битных значения могут иметь разные семантики и разные ограничения на битовые шаблоны; передача такого значения формально может пройти, но последующая интерпретация уже нарушит инварианты Rust.
Объявление FFI следует строить по заголовочному файлу, официальной спецификации ABI и подтверждённому layout, а не по внешнему сходству типов. Для структур обычно требуется C-совместимое представление, а для сложных или непрозрачных объектов безопаснее использовать указатель на тип, чьё внутреннее устройство не обещается Rust-коду.
Безопасная обёртка должна скрывать unsafe только после проверки внешнего контракта: она преобразует входы, валидирует результаты и не позволяет вызывающему коду выразить недопустимое состояние. Если библиотека имеет несколько ABI или платформенные варианты сигнатуры, обёртка должна учитывать их явно, а не полагаться на совпадение размеров.
Библиотека объявляет функцию, принимающую C-структуру с двумя полями. В Rust её ошибочно заменяют на кортежный тип того же общего размера. В тестах на одной платформе вызов возвращает ожидаемый результат, но на другой поля передаются иначе из-за различий в layout или правилах ABI.
Можно передавать структуру как есть. Это просто, но требует доказать её представление и способ передачи на всех поддерживаемых платформах. Можно передавать указатель на непрозрачный объект; это уменьшает зависимость от layout, но требует контролировать время жизни, изменяемость и освобождение объекта.
Предпочтительно использовать точно описанный C-совместимый тип для публичной структуры и отдельную безопасную обёртку для указателей на непрозрачные объекты. Такой вариант явно фиксирует ABI-контракт, не раскрывает лишние внутренние детали и позволяет централизованно проверять ошибки и владение. В результате тесты проверяют не только размер типов, но и layout, выравнивание, calling convention и поведение на целевых платформах.
Нет. Нужно подтвердить ABI, layout, способ передачи и возврата, допустимые значения и семантику параметра. Размер и выравнивание устраняют только часть возможных несовместимостей.
Нет, если это меняет ABI или представление. Например, логическое значение, перечисление и целое число могут иметь разные правила допустимых значений и передачи, даже если в конкретном вызове используется маленькое число. Тип в объявлении должен соответствовать фактическому контракту библиотеки, а преобразование выполняется явно в проверяемой обёртке.
При обычном преобразовании Rust-код контролирует момент, формат и результат преобразования. При неверной FFI-сигнатуре ошибка затрагивает сам механизм вызова: аргументы или возвращаемое значение могут быть извлечены не из тех регистров или областей памяти. Поэтому последствия могут проявиться как повреждение памяти или неопределённое поведение ещё до того, как Rust получит возможность проверить значение.