[2026] C++ and Rust Interoperability: FFI, C ABI, bindgen, cxx, and Memory Safety
이 글의 핵심
Master C++ and Rust interop: FFI, C ABI, bindgen, cxx, memory safety, and production patterns for hybrid codebases.
Why C++ and Rust Interoperability Matters
Problem: Hybrid Codebases
Problem: You have:
- Existing C++ codebase (millions of lines)
- Need Rust’s memory safety for new features
- Cannot rewrite everything in Rust Solution: FFI (Foreign Function Interface) via C ABI bridge. 아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart LR
subgraph Rust
R[Rust Code]
end
subgraph C ABI
C["C ABI Bridge\nextern C"]
end
subgraph C++
CPP[C++ Code]
end
R <-->|FFI| C
C <-->|FFI| CPP
Table of Contents
- FFI Fundamentals
- C ABI Bridge
- bindgen: Generate Rust Bindings
- cxx: Safe C++/Rust Interop
- Memory Safety Patterns
- Ownership Transfer
- Error Handling
- Production Patterns
- Complete Example
1. FFI Fundamentals
What is FFI?
FFI (Foreign Function Interface): Mechanism to call functions written in one language from another.
C ABI: Universal Bridge
C ABI (Application Binary Interface): Standard calling convention and data layout.
- C++:
extern "C"disables name mangling - Rust:
extern "C"uses C calling convention 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// C++ side
extern "C" {
int add(int a, int b) {
return a + b;
}
}
아래 코드는 rust를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Rust side
extern "C" {
fn add(a: i32, b: i32) -> i32;
}
fn main() {
unsafe {
let result = add(5, 3);
println!("5 + 3 = {}", result); // 8
}
}
Key: C ABI is the bridge between C++ and Rust.
2. C ABI Bridge
Calling C++ from Rust
C++ Library
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// math.h
#ifndef MATH_H
#define MATH_H
#ifdef __cplusplus
extern "C" {
#endif
int add(int a, int b);
int multiply(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// math.cpp
#include "math.h"
extern "C" {
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
}
Rust Bindings
아래 코드는 rust를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// build.rs
fn main() {
cc::Build::new()
.file("math.cpp")
.compile("math");
}
다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// lib.rs
extern "C" {
fn add(a: i32, b: i32) -> i32;
fn multiply(a: i32, b: i32) -> i32;
}
pub fn safe_add(a: i32, b: i32) -> i32 {
unsafe { add(a, b) }
}
pub fn safe_multiply(a: i32, b: i32) -> i32 {
unsafe { multiply(a, b) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(safe_add(2, 3), 5);
}
#[test]
fn test_multiply() {
assert_eq!(safe_multiply(4, 5), 20);
}
}
Key: Wrap unsafe FFI calls in safe Rust functions.
Calling Rust from C++
Rust Library
아래 코드는 rust를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// lib.rs
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn rust_multiply(a: i32, b: i32) -> i32 {
a * b
}
C++ Usage
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// main.cpp
#include <iostream>
extern "C" {
int rust_add(int a, int b);
int rust_multiply(int a, int b);
}
int main() {
std::cout << "5 + 3 = " << rust_add(5, 3) << "\n"; // 8
std::cout << "4 * 5 = " << rust_multiply(4, 5) << "\n"; // 20
}
Build: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Build Rust library
cargo build --release
# Link with C++
g++ main.cpp -L target/release -lrust_math -o main
./main
Output:
5 + 3 = 8
4 * 5 = 20
Key: #[no_mangle] prevents Rust name mangling; extern "C" uses C ABI.
3. bindgen: Generate Rust Bindings
What is bindgen?
bindgen: Automatically generates Rust FFI bindings from C/C++ headers.
Installation
cargo install bindgen-cli
Example: Binding C++ Class
C++ Header
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// vector.h
#ifndef VECTOR_H
#define VECTOR_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Vector Vector;
Vector* vector_new();
void vector_delete(Vector* v);
void vector_push(Vector* v, int value);
int vector_get(const Vector* v, int index);
int vector_size(const Vector* v);
#ifdef __cplusplus
}
#endif
#endif
C++ Implementation
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// vector.cpp
#include "vector.h"
#include <vector>
struct Vector {
std::vector<int> data;
};
extern "C" {
Vector* vector_new() {
return new Vector();
}
void vector_delete(Vector* v) {
delete v;
}
void vector_push(Vector* v, int value) {
v->data.push_back(value);
}
int vector_get(const Vector* v, int index) {
return v->data[index];
}
int vector_size(const Vector* v) {
return v->data.size();
}
}
Generate Bindings
bindgen vector.h -o bindings.rs
Rust Wrapper
다음은 rust를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// lib.rs
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub struct SafeVector {
ptr: *mut Vector,
}
impl SafeVector {
pub fn new() -> Self {
unsafe {
Self {
ptr: vector_new(),
}
}
}
pub fn push(&mut self, value: i32) {
unsafe {
vector_push(self.ptr, value);
}
}
pub fn get(&self, index: usize) -> i32 {
unsafe {
vector_get(self.ptr, index as i32)
}
}
pub fn len(&self) -> usize {
unsafe {
vector_size(self.ptr) as usize
}
}
}
impl Drop for SafeVector {
fn drop(&mut self) {
unsafe {
vector_delete(self.ptr);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector() {
let mut vec = SafeVector::new();
vec.push(10);
vec.push(20);
vec.push(30);
assert_eq!(vec.len(), 3);
assert_eq!(vec.get(0), 10);
assert_eq!(vec.get(1), 20);
assert_eq!(vec.get(2), 30);
}
}
Key: bindgen generates unsafe bindings; wrap in safe Rust API.
4. cxx: Safe C++/Rust Interop
What is cxx?
cxx: Safe, zero-cost C++/Rust interop with compile-time checks.
Installation
아래 코드는 toml를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Cargo.toml
[dependencies]
cxx = "1.0"
[build-dependencies]
cxx-build = "1.0"
Example: Shared Types
Bridge Definition
다음은 rust를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// src/lib.rs
#[cxx::bridge]
mod ffi {
// Shared types
struct Point {
x: i32,
y: i32,
}
// C++ functions
extern "C++" {
include!("geometry.h");
fn distance(p1: &Point, p2: &Point) -> f64;
}
// Rust functions
extern "Rust" {
fn midpoint(p1: &Point, p2: &Point) -> Point;
}
}
// Rust implementation
fn midpoint(p1: &ffi::Point, p2: &ffi::Point) -> ffi::Point {
ffi::Point {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2,
}
}
C++ Implementation
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// geometry.h
#pragma once
#include "lib.rs.h"
double distance(const Point& p1, const Point& p2);
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// geometry.cpp
#include "geometry.h"
#include <cmath>
double distance(const Point& p1, const Point& p2) {
int dx = p2.x - p1.x;
int dy = p2.y - p1.y;
return std::sqrt(dx * dx + dy * dy);
}
Build Script
아래 코드는 rust를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// build.rs
fn main() {
cxx_build::bridge("src/lib.rs")
.file("geometry.cpp")
.compile("geometry");
}
Usage
아래 코드는 rust를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// main.rs
use crate::ffi::Point;
fn main() {
let p1 = Point { x: 0, y: 0 };
let p2 = Point { x: 3, y: 4 };
let dist = ffi::distance(&p1, &p2);
println!("Distance: {}", dist); // 5.0
let mid = midpoint(&p1, &p2);
println!("Midpoint: ({}, {})", mid.x, mid.y); // (1, 2)
}
Key: cxx provides type-safe, zero-cost interop with compile-time checks.
5. Memory Safety Patterns
Pattern 1: Opaque Pointers
다음은 rust를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Rust side
pub struct OpaqueHandle {
_private: [u8; 0],
}
#[no_mangle]
pub extern "C" fn create_handle() -> *mut OpaqueHandle {
Box::into_raw(Box::new(MyStruct::new())) as *mut OpaqueHandle
}
#[no_mangle]
pub extern "C" fn destroy_handle(handle: *mut OpaqueHandle) {
if !handle.is_null() {
unsafe {
let _ = Box::from_raw(handle as *mut MyStruct);
}
}
}
Key: Hide implementation details behind opaque pointer.
Pattern 2: Lifetime Validation
다음은 rust를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Rust side
#[repr(C)]
pub struct Buffer {
data: *const u8,
len: usize,
}
#[no_mangle]
pub extern "C" fn get_buffer(s: &str) -> Buffer {
Buffer {
data: s.as_ptr(),
len: s.len(),
}
}
// C++ side must not outlive Rust string
Key: Document lifetime constraints in FFI boundary.
Pattern 3: Null Pointer Checks
아래 코드는 rust를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#[no_mangle]
pub extern "C" fn process_data(data: *const u8, len: usize) -> i32 {
if data.is_null() {
return -1; // Error code
}
unsafe {
let slice = std::slice::from_raw_parts(data, len);
// Process slice
0 // Success
}
}
Key: Always validate pointers at FFI boundary.
6. Ownership Transfer
C++ → Rust
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++ side: transfer ownership
extern "C" {
void rust_take_ownership(int* data);
}
void transfer() {
int* data = new int(42);
rust_take_ownership(data); // Rust now owns data
// Do NOT delete data here
}
아래 코드는 rust를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Rust side: take ownership
#[no_mangle]
pub extern "C" fn rust_take_ownership(data: *mut i32) {
if !data.is_null() {
unsafe {
let boxed = Box::from_raw(data);
println!("Received: {}", *boxed);
// boxed dropped here, memory freed
}
}
}
Rust → C++
아래 코드는 rust를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Rust side: transfer ownership
#[no_mangle]
pub extern "C" fn rust_create_data() -> *mut i32 {
Box::into_raw(Box::new(42))
}
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++ side: take ownership
extern "C" {
int* rust_create_data();
}
void receive() {
int* data = rust_create_data();
std::cout << "Received: " << *data << "\n";
delete data; // C++ must free
}
Key: Document ownership transfer clearly in API.
7. Error Handling
Pattern 1: Error Codes
다음은 rust를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#[repr(C)]
pub enum ErrorCode {
Success = 0,
InvalidInput = 1,
OutOfMemory = 2,
Unknown = 3,
}
#[no_mangle]
pub extern "C" fn process(input: *const u8, len: usize) -> ErrorCode {
if input.is_null() {
return ErrorCode::InvalidInput;
}
unsafe {
let slice = std::slice::from_raw_parts(input, len);
match do_process(slice) {
Ok(_) => ErrorCode::Success,
Err(_) => ErrorCode::Unknown,
}
}
}
Pattern 2: Out Parameters
아래 코드는 rust를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#[no_mangle]
pub extern "C" fn divide(a: i32, b: i32, result: *mut i32) -> bool {
if b == 0 || result.is_null() {
return false;
}
unsafe {
*result = a / b;
}
true
}
int result;
if (divide(10, 2, &result)) {
std::cout << "Result: " << result << "\n"; // 5
} else {
std::cerr << "Error\n";
}
Pattern 3: Exception Boundary
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++ side: catch exceptions at FFI boundary
extern "C" int cpp_function_that_may_throw(int x) {
try {
return risky_operation(x);
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
return -1; // Error code
} catch (...) {
std::cerr << "Unknown exception\n";
return -1;
}
}
Key: Never let exceptions cross FFI boundary—catch and convert to error codes.
8. Production Patterns
Pattern 1: String Conversion
다음은 rust를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn rust_process_string(s: *const c_char) -> *mut c_char {
if s.is_null() {
return std::ptr::null_mut();
}
unsafe {
let c_str = CStr::from_ptr(s);
let rust_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let result = rust_str.to_uppercase();
match CString::new(result) {
Ok(c_string) => c_string.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
}
#[no_mangle]
pub extern "C" fn rust_free_string(s: *mut c_char) {
if !s.is_null() {
unsafe {
let _ = CString::from_raw(s);
}
}
}
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
extern "C" {
char* rust_process_string(const char* s);
void rust_free_string(char* s);
}
void test() {
char* result = rust_process_string("hello");
if (result) {
std::cout << result << "\n"; // HELLO
rust_free_string(result);
}
}
Key: Use CString/CStr for C-compatible strings; provide free function.
Pattern 2: Callback Functions
다음은 rust를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
type Callback = extern "C" fn(i32) -> i32;
#[no_mangle]
pub extern "C" fn rust_map(data: *const i32, len: usize, callback: Callback, out: *mut i32) {
if data.is_null() || out.is_null() {
return;
}
unsafe {
let input = std::slice::from_raw_parts(data, len);
let output = std::slice::from_raw_parts_mut(out, len);
for (i, &value) in input.iter().enumerate() {
output[i] = callback(value);
}
}
}
다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
extern "C" {
void rust_map(const int* data, size_t len, int (*callback)(int), int* out);
}
extern "C" int double_value(int x) {
return x * 2;
}
void test() {
int data[] = {1, 2, 3, 4, 5};
int out[5];
rust_map(data, 5, double_value, out);
for (int i = 0; i < 5; ++i) {
std::cout << out[i] << " "; // 2 4 6 8 10
}
}
Key: Use function pointers for callbacks across FFI.
Pattern 3: Shared Memory
다음은 rust를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#[repr(C)]
pub struct SharedData {
pub counter: std::sync::atomic::AtomicI32,
pub flag: std::sync::atomic::AtomicBool,
}
#[no_mangle]
pub extern "C" fn create_shared_data() -> *mut SharedData {
Box::into_raw(Box::new(SharedData {
counter: std::sync::atomic::AtomicI32::new(0),
flag: std::sync::atomic::AtomicBool::new(false),
}))
}
#[no_mangle]
pub extern "C" fn increment_counter(data: *mut SharedData) {
if !data.is_null() {
unsafe {
(*data).counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
}
Key: Use atomic types for thread-safe shared memory.
9. Complete Example
Rust Library
다음은 rust를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[repr(C)]
pub struct Image {
width: u32,
height: u32,
data: *mut u8,
}
#[no_mangle]
pub extern "C" fn image_create(width: u32, height: u32) -> *mut Image {
let size = (width * height * 4) as usize;
let mut data = vec![0u8; size];
let data_ptr = data.as_mut_ptr();
std::mem::forget(data);
Box::into_raw(Box::new(Image {
width,
height,
data: data_ptr,
}))
}
#[no_mangle]
pub extern "C" fn image_destroy(img: *mut Image) {
if !img.is_null() {
unsafe {
let img = Box::from_raw(img);
let size = (img.width * img.height * 4) as usize;
let _ = Vec::from_raw_parts(img.data, size, size);
}
}
}
#[no_mangle]
pub extern "C" fn image_fill(img: *mut Image, r: u8, g: u8, b: u8, a: u8) {
if img.is_null() {
return;
}
unsafe {
let img = &*img;
let size = (img.width * img.height * 4) as usize;
let data = std::slice::from_raw_parts_mut(img.data, size);
for i in (0..size).step_by(4) {
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
}
}
#[no_mangle]
pub extern "C" fn image_save(img: *const Image, path: *const c_char) -> bool {
if img.is_null() || path.is_null() {
return false;
}
unsafe {
let img = &*img;
let c_str = CStr::from_ptr(path);
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return false,
};
// Save image (simplified)
println!("Saving {}x{} image to {}", img.width, img.height, path_str);
true
}
}
C++ Usage
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// main.cpp
#include <iostream>
extern "C" {
struct Image;
Image* image_create(uint32_t width, uint32_t height);
void image_destroy(Image* img);
void image_fill(Image* img, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
bool image_save(const Image* img, const char* path);
}
int main() {
// Create 800x600 image
Image* img = image_create(800, 600);
// Fill with red
image_fill(img, 255, 0, 0, 255);
// Save
if (image_save(img, "output.png")) {
std::cout << "Image saved successfully\n";
} else {
std::cerr << "Failed to save image\n";
}
// Cleanup
image_destroy(img);
return 0;
}
Build: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Build Rust library
cargo build --release
# Compile C++
g++ main.cpp -L target/release -limage_lib -o main
# Run
./main
Output:
Saving 800x600 image to output.png
Image saved successfully
Key: Complete FFI example with resource management and error handling.
Summary
Key Concepts
| Concept | Description |
|---|---|
| FFI | Foreign Function Interface |
| C ABI | Universal bridge between languages |
| bindgen | Generate Rust bindings from C/C++ |
| cxx | Safe, zero-cost C++/Rust interop |
| Memory safety | Validate pointers, document ownership |
| C++ and Rust interop requires C ABI bridge, careful memory management, and clear ownership semantics. |
Keywords
C++ Rust interop, FFI, C ABI, bindgen, cxx, memory safety, ownership transfer One-line summary: Master C++ and Rust interoperability using FFI, C ABI, bindgen, and cxx for safe, zero-cost hybrid codebases with clear ownership semantics.