ПрограммированиеСистемный разработчик

Поясните, как работает system FFI (Foreign Function Interface) в Rust. Какие требования и подводные камни существуют для безопасного вызова функций из C?

Проходите собеседования с ИИ помощником Hintsage

Ответ

FFI в Rust позволяет вызывать функции, объявленные во внешних библиотеках (например, C/C++), и экспортировать Rust-функции наружу. Для этого используется ключевое слово extern. Требования:

  • Все FFI вызовы должны быть обёрнуты в объявление с unsafe;
  • ABI (Application Binary Interface) должен совпадать (обычно "C");
  • Типы данных должны быть совместимы — важно использовать примитивы с фиксированными размерами (i32, u64 и др.);
  • Управление памятью — очень внимательно с владением, утечками и двойным освобождением.

Пример обертки:

extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("{}", abs(-5)); } }

Экспортировать функцию из Rust для использования в C можно так:

#[no_mangle] pub extern "C" fn sum(a: i32, b: i32) -> i32 { a + b }

Вопрос с подвохом

Вопрос: Гарантирует ли Rust, что если обёрнуть вызов C функции в unsafe, всё будет работать корректно в плане потокобезопасности и отлова UB?

Ответ: Нет! unsafe — это обещание компилятору, что вы сами ручаетесь за корректность всех требований безопасности (aliasing, потокобезопасность, доступ к памяти). Rust не проводит проверок кода внутри C. Например, race condition или UB в коде библиотеки может "сломать" runtime Rust-программы. Даже если Rust-код компилируется, реальное выполнение может привести к аварийному завершению.


История

В финансовом дата-провайдере команда интегрировала C-библиотеку через FFI, не проверяя размеры типов. На x86_64 ожидалось, что long и i64 совпадают, но оказалось, что размеры не совпали на других платформах — неправильное чтение памяти привело к багам в расчётах.

История

Разработчик создал обёртку для C API, забыв про передаваемые указатели на буферы. Т.к. Rust автоматически освобождал память, C-функция иногда работала с уже освобождённой, приводя к сломам и падениям.

История

В клиент‑серверном продукте Rust модуль обращался к C-библиотеке, не учтя multi-thread safety. Библиотека не была thread-safe, а Rust-проги обращались к ней из разных потоков одновременно, что вылилось в краши и повреждение данных.