bindgen이란?
C/C++ 헤더 파일(.h)을 읽어서 Rust FFI 바인딩 코드를 자동으로 생성해주는 도구다.
왜 쓰는가
C 라이브러리를 Rust에서 쓰려면 extern "C" 블록에 함수 시그니처, 구조체, 상수 등을 일일이 선언해야 한다.
Rust
extern "C" {
fn whisper_init(path: *const c_char) -> *mut whisper_context;
fn whisper_free(ctx: *mut whisper_context);
// ... 수십 개의 함수를 수동으로 작성
}
헤더 파일이 크면 이걸 손으로 쓰는 건 비현실적이고, 오타 하나로 메모리 버그가 생길 수 있다. bindgen은 이 과정을 자동화한다.
어떻게 동작하는가
- C/C++ 헤더 파일을 libclang으로 파싱한다
- 함수, 구조체, enum, 상수, typedef 등을 분석한다
- 대응하는 Rust FFI 코드를 생성한다
TEXT
whisper.h → [bindgen + libclang] → bindings.rs
사용법
1. 의존성 추가
TOML
[build-dependencies]
bindgen = "0.71"
2. build.rs에서 바인딩 생성
Rust
let bindings = bindgen::Builder::default()
.header("whisper.cpp/include/whisper.h")
.clang_arg("-Iwhisper.cpp/ggml/include") // 추가 include 경로
.generate()
.expect("bindgen 실패");
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
bindings.write_to_file(out_path.join("bindings.rs")).unwrap();
3. lib.rs에서 가져오기
Rust
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
allow 속성은 C 스타일 이름(whisper_full_params 등)이 Rust 네이밍 컨벤션 경고를 발생시키기 때문에 필요하다.
생성되는 코드 예시
헤더에 이런 C 코드가 있으면:
C
struct whisper_context;
int whisper_full(struct whisper_context * ctx, struct whisper_full_params params, const float * samples, int n_samples);
bindgen이 이런 Rust 코드를 생성한다:
Rust
#[repr(C)]
pub struct whisper_context { _unused: [u8; 0] }
extern "C" {
pub fn whisper_full(
ctx: *mut whisper_context,
params: whisper_full_params,
samples: *const f32,
n_samples: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
OUT_DIR이란?
OUT_DIR은 Cargo가 빌드 스크립트에 제공하는 환경변수로, 빌드 중 생성되는 파일을 저장하는 임시 디렉토리다. target/debug/build/<crate>/out/ 같은 경로를 가리킨다.
생성된 파일은 소스 트리를 오염시키지 않고, include! 매크로로 컴파일 타임에 가져온다.
주의할 점
- bindgen은 내부적으로 libclang을 사용하므로, 시스템에 clang이 설치되어 있어야 한다 (macOS는 Xcode Command Line Tools에 포함).
- C++ 템플릿은 바인딩이 제대로 생성되지 않을 수 있다. C 인터페이스(
extern "C")를 대상으로 쓰는 것이 가장 안정적이다. - 생성된 코드는 모두
unsafe다. safe API로 감싸는 것은 별도의 wrapper crate에서 한다.