advanced⏱90 minutosLição 6 de 10
FFI e Interoperabilidade com C
Chame C a partir de Rust com FFI, use blocos extern, gerencie memória entre linguagens e automatize bindings com bindgen
FFI e Interoperabilidade com C
Rust torna fácil interoperar com C através de uma Interface de Função Estrangeira (FFI) bem definida. Isso permite usar bibliotecas C existentes e expor código Rust para outras linguagens.
A Palavra-chave extern
rust
// Declarar funções C externas
extern "C" {
fn puts(s: *const u8) -> i32;
fn strlen(s: *const u8) -> usize;
}
fn main() {
let msg = b"Hello from Rust\0";
unsafe {
puts(msg.as_ptr());
let len = strlen(msg.as_ptr());
println!("length: {len}");
}
}Note
Padrão de Wrapper Seguro
rust
use std::ffi::{CStr, CString};
use std::os::raw::c_int;
// Declaração FFI insegura
extern "C" {
fn abs(x: c_int) -> c_int;
}
// Wrapper seguro
fn safe_abs(x: i32) -> i32 {
unsafe { abs(x) }
}
// Exemplo com string
extern "C" {
fn strlen(s: *const u8) -> usize;
}
fn safe_strlen(s: &str) -> usize {
let c_string = CString::new(s).expect("null byte in string");
unsafe { strlen(c_string.as_ptr()) }
}
fn main() {
println!("abs(-5): {}", safe_abs(-5));
println!("len('hello'): {}", safe_strlen("hello"));
}Warning
CString::new() retorna um erro se a entrada contém um byte nulo. Strings C são terminadas em nulo, então nulls internos truncariam a string.
Vinculando a Bibliotecas C
Ligação Estática
rust
// Ligar a libm estaticamente
#[link(name = "m", kind = "static")]
extern "C" {
fn sqrt(x: f64) -> f64;
fn pow(x: f64, y: f64) -> f64;
}Ligação Dinâmica
rust
// Ligar a libm dinamicamente (padrão)
#[link(name = "m")]
extern "C" {
fn sin(x: f64) -> f64;
fn cos(x: f64) -> f64;
}Script de Build
rust
// build.rs — executado antes de cargo build
fn main() {
// Ligar a uma biblioteca C
println!("cargo:rustc-link-search=/path/to/lib");
println!("cargo:rustc-link-lib=my_c_lib");
// Re-executar se o código-fonte da biblioteca mudar
println!("cargo:rerun-if-changed=src/c_library.c");
// Compilar código C junto com Rust
cc::Build::new()
.file("src/c_helper.c")
.compile("c_helper");
}toml
# Cargo.toml
[build-dependencies]
cc = "1.0" // Para compilar código CGerenciamento de Memória entre FFI
Regras de Ownership
rust
use std::ffi::CString;
extern "C" {
// Retorna uma string alocada — Rust deve liberá-la
fn get_message() -> *mut u8;
// Assume ownership da string alocada
fn free_message(ptr: *mut u8);
}
fn safe_get_message() -> String {
let ptr = unsafe { get_message() };
if ptr.is_null() {
return String::new();
}
// Converter para string Rust
let c_str = unsafe { CStr::from_ptr(ptr as *const i8) };
let result = c_str.to_string_lossy().into_owned();
// Liberar a memória alocada por C
unsafe { free_message(ptr) };
result
}Success
Passando Alocações Rust para C
rust
use std::mem;
extern "C" fn callback(data: *mut u8, len: usize) {
// Chamado de C — precisamos reconstruir uma slice Rust
let slice = unsafe { std::slice::from_raw_parts(data, len) };
println!("C passed: {:?}", slice);
}
fn main() {
let mut data = vec![1u8, 2, 3, 4];
let ptr = data.as_mut_ptr();
let len = data.len();
// Impedir Rust de dropar os dados enquanto C os usa
mem::forget(data); // NÃO dropar — C liberará
unsafe {
callback(ptr, len);
}
}Representando Structs C
rust
use std::ffi::CStr;
#[repr(C)]
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
#[repr(C)]
struct Person {
name: *const i8, // String C
age: u32,
}
impl Person {
fn safe_name(&self) -> &str {
unsafe { CStr::from_ptr(self.name) }
.to_str()
.unwrap_or("<invalid utf8>")
}
}
extern "C" {
fn make_point(x: f64, y: f64) -> Point;
fn print_person(p: *const Person);
}
fn main() {
unsafe {
let p = make_point(3.0, 4.0);
println!("Point: {:?}", p); // Point { x: 3.0, y: 4.0 }
}
}Warning
#[repr(C)] garante layout compatível com C. Sem ele, Rust pode reordenar campos. Sempre use #[repr(C)] para structs FFI.
Callbacks — Rust para C
rust
// Definir tipo de callback
type Callback = unsafe extern "C" fn(i32) -> i32;
extern "C" {
fn register_callback(cb: Option<Callback>);
fn trigger_callback(x: i32) -> i32;
}
// Função Rust que C pode chamar
unsafe extern "C" fn my_callback(x: i32) -> i32 {
x * 2
}
fn main() {
unsafe {
register_callback(Some(my_callback));
let result = trigger_callback(21);
println!("result: {result}"); // 42
}
}Callbacks Baseadas em Closure
rust
use std::ffi::c_void;
extern "C" {
fn register_handler(ctx: *mut c_void, cb: unsafe extern "C" fn(*mut c_void, i32));
}
unsafe extern "C" fn handler_callback<F: FnMut(i32)>(ctx: *mut c_void, val: i32) {
let handler = &mut *(ctx as *mut F);
handler(val);
}
struct Handler<F: FnMut(i32)> {
callback: F,
}
fn main() {
let mut handler = Handler {
callback: |x| println!("callback: {x}"),
};
let ctx = &mut handler as *mut Handler<_> as *mut c_void;
unsafe {
register_handler(ctx, handler_callback::<Box<dyn FnMut(i32)>>);
}
}bindgen — Bindings Automáticos
Para cabeçalhos C grandes, use bindgen para gerar bindings automaticamente:
toml
[build-dependencies]
bindgen = "0.70"rust
// build.rs
fn main() {
let bindings = bindgen::Builder::default()
.header("wrapper.h") // #include <my_lib.h>
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("unable to generate bindings");
bindings
.write_to_file("src/bindings.rs")
.expect("couldn't write bindings");
}rust
// src/lib.rs
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!("bindings.rs");Exemplo Real: Usando libcurl
rust
use std::ffi::{CStr, CString};
#[repr(C)]
struct curl_slist {
data: *const i8,
next: *mut curl_slist,
}
extern "C" {
fn curl_global_init(flags: i64) -> i32;
fn curl_easy_init() -> *mut std::ffi::c_void;
fn curl_easy_setopt(handle: *mut std::ffi::c_void, opt: i32, param: ...) -> i32;
fn curl_easy_perform(handle: *mut std::ffi::c_void) -> i32;
fn curl_easy_cleanup(handle: *mut std::ffi::c_void);
fn curl_global_cleanup();
}
const CURLOPT_URL: i32 = 10002;
const CURLOPT_FOLLOWLOCATION: i32 = 52;
fn fetch_url(url: &str) -> Result<(), Box<dyn std::error::Error>> {
unsafe {
curl_global_init(3); // CURL_GLOBAL_ALL
let handle = curl_easy_init();
if handle.is_null() {
curl_global_cleanup();
return Err("curl init failed".into());
}
let c_url = CString::new(url)?;
curl_easy_setopt(handle, CURLOPT_URL, c_url.as_ptr());
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1i64);
let res = curl_easy_perform(handle);
curl_easy_cleanup(handle);
curl_global_cleanup();
if res != 0 {
return Err(format!("curl error: {res}").into());
}
}
Ok(())
}Perguntas de Prática
- O que
extern "C"faz em Rust? - Como passar strings entre Rust e C com segurança?
- Qual é o propósito de
#[repr(C)]? - Como vincular uma biblioteca C em um projeto Rust?
- Qual é a diferença entre ligação estática e dinâmica?
- Como criar um wrapper seguro para uma função FFI insegura?
- O que é
bindgene quando usá-lo? - Como funciona a propriedade de memória através de fronteiras FFI?
- O que é um callback em contexto FFI e como é registrado?
- Como impedir Rust de dropar dados que C ainda referencia?
Progresso60%
Contéudo do Curso
1. Threads — spawn, join e Send + Sync2. Passagem de Mensagens — Canais e Mutex Básico3. Estado Compartilhado — Mutex, RwLock, Arc e Atômicos4. Async/Await e Tokio5. Unsafe Rust6. FFI e Interoperabilidade com C7. Macros Declarativas (macro_rules!)8. Macros Procedurais9. Smart Pointers — Box, Rc, RefCell e Mutabilidade Interior10. Capstone Avançado — Armazenamento Chave-Valor Concorrente