Rust/C++ 상호운용성의 현실과 도전 — Victor Ciura @ CppCon 2025

📺 원본 영상: Rust/C++ Interop Challenges - Victor Ciura - CppCon 2025 🎙️ 채널: CppCon 📅 정리 일자: 2026-04-07 ⚠️ Note: 본 리포트는 영상의 음성을 텍스트로 변환(STT)하여 정리한 것입니다. 화면에 표시된 자료(차트, 코드, 슬라이드 등)는 포함되지 않을 수 있습니다.


개요

Rust 코드가 산업 전반에서 급격히 확산되면서, 수억 줄의 기존 C++ 코드베이스와 공존해야 하는 현실이 도래했다. 이 강연에서 Victor Ciura는 Rust와 C++의 상호운용성(Interop)이 단순한 기술적 호기심을 넘어 실용적 생존 전략이 된 이유를 짚고, 현존하는 도구들의 설계 철학과 각각의 한계를 솔직하게 분석한다. “고신뢰도 상호운용성(high-fidelity interoperability)은 아직 달성되지 않았다”는 선언 하에, 언어 의미론적 불일치, ABI 안정성 문제, 그리고 컴파일러 수준의 개입 필요성까지 폭넓게 다룬다.


왜 지금 Rust/C++ 상호운용성인가

오늘날 많은 기업의 코드베이스는 단일 언어로 구성되지 않는다. Rust 코드의 비중은 가속도적으로 증가하고 있고, C++도 수십억 줄의 레거시와 함께 여전히 활발하게 작성되고 있다. 강연자는 이 둘이 뒤섞인 하이브리드 코드베이스가 “우리가 원하든 원하지 않든 미래의 표준”이 될 것이라고 단언한다. Rust 개발자가 C++로의 interop을 고려하는 이유는 주로 아직 Rust로 포팅되지 않은 기존 라이브러리에 접근해야 하기 때문이다. 반면 C++ 개발자가 Rust interop을 고려할 때는 “호기심보다는 절박함”이 동기가 되는 경우가 많다고 강연자는 짚는다. 핵심적인 보안 취약 코드를 Rust로 교체하려는 재정적 인센티브가 가장 강력한 동인이라는 것이다.

어떤 팀이나 프로젝트가 Rust/C++ Interop을 이야기할 때, 그들이 의미하는 바는 제각각이다. 누군가는 마샬링 비용과 불필요한 복사를 없애고 싶어하고, 누군가는 보일러플레이트 코드와 어노테이션 없이 깔끔한 타입 지원을 원한다. 동적 라이브러리 지원이 절실한 팀이 있는가 하면, 자동화된 동기화 메커니즘 없이는 interop 경계를 유지하기가 너무 고통스럽다는 팀도 있다. 이처럼 다양한 니즈가 존재하기 때문에, 강연자는 “모든 상황에 맞는 단일 해법은 없다”는 전제를 강연 전반에 걸쳐 강조한다.


기존 솔루션 전경: C FFI에서 Crubit까지

C: 영원한 공통 언어, 그리고 그 한계

가장 원초적인 interop 방법은 C를 매개로 하는 것이다. C는 사실상 모든 FFI 시스템의 공통 언어(lingua franca)로서, 어떤 언어든 C를 통해 다른 시스템과 연결될 수 있다. 하지만 이 접근은 동시에 가장 많은 것을 희생한다. 프라이빗 필드 없는 나체(naked) 구조체, 어디서나 노출된 퍼블릭 필드, 수동 라이프타임 관리, 안전성 보장 전무. 강연자는 이를 “아무도 의도적으로 선택하고 싶지 않은 절박한 수단”이라고 표현한다.

Bindgen과 cbindgen: C 계층을 통한 자동화

Bindgen은 C 헤더 파일로부터 Rust FFI 바인딩을 자동으로 생성해 준다. 반대 방향인 cbindgen은 Rust 소스에서 C 헤더를 생성한다.

Bindgen이 C 헤더로부터 Rust FFI 바인딩을 생성하는 예시 코드 슬라이드

08:42 - Bindgen이 C 헤더로부터 Rust FFI 바인딩을 생성하는 예시 코드. ReprC와 extern C 블록이 표시되어 있음

두 도구 모두 소스 파일에 직접 작동하므로 별도의 IR(중간 표현)을 다룰 필요가 없다는 장점이 있다. 그러나 양쪽 모두 C ABI로 “낮추는(lowering)” 과정을 거치기 때문에 근본적인 한계를 공유한다. 값 전달(pass by value)은 C 기본 타입으로만 제한되며, 프라이빗 필드가 있는 구조체, unique_ptr, optional 같은 C++ 고급 타입은 물론이고, Rust 측에서는 Box, Option, Rust 열거형, 문자열 슬라이스, 슬라이스 일반 등 이디엄적인 Rust 타입을 모두 잃게 된다. 특히 Rust 슬라이스의 메모리 레이아웃이 언어 명세 상 보장되지 않기 때문에, 슬라이스조차 이 interop 메커니즘을 통해 표현할 수 없다는 점은 실용적으로 큰 제약이다.

CXX: 성숙한 매크로 기반 IDL

CXX는 현재 C++ 의미론이 필요한 대부분의 팀이 가장 먼저 손을 뻗는 성숙한 솔루션이다. 이 라이브러리는 매크로 기반 IDL(Interface Definition Language)을 사용하는데, Rust 코드 내에서 cxx::bridge 매크로를 통해 FFI 모듈을 정의하면 양쪽 언어에서 사용할 공유 타입과 함수가 그 안에 선언된다. CXX의 접근 방식은 점진적 도입이 가능하다는 점에서 유용하다. 즉, interop 경계를 넘겨야 하는 타입과 함수만 선택적으로 IDL에 선언하면 된다.

CXX bridge 매크로를 사용한 FFI 모듈 IDL 예시

12:08 - CXX bridge 매크로를 사용한 FFI 모듈 IDL 예시. 공유 타입과 함수가 선언된 Rust 코드와 C++ 코드 양쪽 표현이 표시되어 있음

CXX가 제공하는 타입 매핑의 폭은 상당히 넓다. Rust의 Box<T>와 C++의 unique_ptr, Optional과 Option, 문자열 슬라이스와 std::string, Vec과 std::vector, 그리고 일반 슬라이스까지 대부분의 표준 타입이 직관적인 방식으로 매핑된다. 그러나 CXX는 사용자 정의 타입의 메모리 레이아웃을 알지 못한다. 이 때문에 값 전달이 불가능하고, 모든 타입을 박싱(boxing)해야 하며, 자기 참조 구조체(self-referential structure)를 위해 Rust의 핀닝(Pinning) 메커니즘에 크게 의존하게 된다. Rust에서 핀닝은 원래 Future 같은 매우 특수한 경우에만 사용되는 무거운 도구인데, CXX를 사용하면 C++ 측의 주소 안정성 요구사항이 Rust 코드 전반을 오염시키는 결과를 낳는다. 표현력이 떨어지고, 이디엄적인 Rust 코드와 거리가 멀어진다. 또한 CXX IDL을 자동으로 유지·동기화해 주는 도구가 없어, 이는 전적으로 수동 작업에 의존한다.

Zengar: IDL에 레이아웃 정보를 더하다

Zengar는 CXX보다 덜 알려진 비교적 최신 솔루션으로, .zng 확장자의 커스텀 IDL 파일을 사용한다. Zengar의 핵심 차별점은 IDL에서 타입의 레이아웃(layout)과 정렬(alignment)을 명시적으로 지정할 수 있다는 점이다. 이 정보를 컴파일러가 알게 되므로, CXX에서는 불가능했던 값 전달(pass by value)이 가능해진다. 덕분에 힙 할당과 박싱에 대한 의존도가 줄고, 핀닝의 필요성도 감소한다. 실제 사용자 경험 측면에서도 CXX에 비해 더 개발자 친화적이고 유연하다는 평가를 받는다.

Zengar의 .zng IDL 파일 예시

16:27 - Zengar의 .zng IDL 파일 예시. 레이아웃과 정렬 정보가 명시된 타입 선언이 화면에 표시되어 있음

그러나 이 장점의 이면에는 더 날카로운 책임이 따른다. 레이아웃 정보를 IDL에 수동으로 선언해야 하므로, interop 경계 양쪽의 타입이 변경될 때마다 IDL 파일도 빠짐없이 업데이트되어야 한다. 이 수동 동기화가 실패하면, 도구가 일부 검증을 수행하긴 하지만 완벽한 방어막은 되지 못한다. 여전히 자동화가 없는 수작업 영역이다.

Crubit: 고신뢰도 상호운용성을 향한 도전

Crubit은 현존하는 솔루션들 중 가장 야심찬 목표를 가진 프로젝트로, Rust와 C++ 간의 고신뢰도 언어 interop을 목표로 한다. 앞선 솔루션들이 IDL 파일을 수동으로 유지해야 하는 것과 달리, Crubit은 컴파일러 깊숙이 통합되어 소스 파일에서 직접 작동하기 때문에 IDL 수동 관리가 필요 없다. C++ 헤더만 있으면 Crubit이 자동으로 Rust API를 생성해 준다.

Crubit의 동작 흐름 도식

19:20 - Crubit의 동작 흐름 도식. C++ 헤더에서 자동으로 Rust API가 생성되는 파이프라인 다이어그램이 표시되어 있음

Crubit이 이를 가능하게 하는 핵심 원리는 컴파일러 통합을 통한 타입 정보 자동 파악이다. 개발자가 IDL에 타입의 크기와 정렬을 직접 기술하지 않아도 컴파일러가 이를 자동으로 알아내기 때문에, 모든 타입에 대해 값 전달이 기본 지원된다. 현재는 Clang과 rustc 조합만 지원하지만, MSVC와 GCC를 지원하는 로드맵이 계획 중이다. 그 방법으로는 C++ 컴파일러로부터 IDL을 자동 생성한 후, Crubit이 이를 소화하는 방식이 검토되고 있다.

Crubit의 접근 방식은 “모든 것을 소스에서 한 번에 빌드”하는 워크플로우에 최적화되어 있다. 거대한 API 표면 전체를 자동으로 Rust에서 접근 가능하게 만들고 싶을 때 강력한 선택지다. 반면, 이 특성 때문에 동적 라이브러리나 미리 컴파일된 바이너리와의 연동은 구조적으로 어렵고, Rust ABI의 불안정성 때문에 버전 간 호환성도 매번 재컴파일로 해결해야 한다.


솔루션 스펙트럼: 설계 철학의 트레이드오프

강연자는 기존 솔루션들을 하나의 스펙트럼으로 시각화한다.

Interop 솔루션 스펙트럼 다이어그램

22:06 - Interop 솔루션 스펙트럼 다이어그램. 툴체인 독립적 솔루션(CXX, Zengar)과 컴파일러 통합 솔루션(Crubit)이 양 극단에 위치한 스펙트럼 차트

한 쪽 극단에는 CXX와 Zengar처럼 툴체인 독립적이고, 외부로 노출하는 API를 개발자가 세밀하게 제어할 수 있으며, ABI 안정성과 동적 링킹을 지원하는 솔루션들이 있다. 이들은 수동 유지보수의 부담이 있지만 특정 함수와 타입만 선택적으로 노출할 수 있어 API 표면을 최소화할 수 있다.

반대쪽 극단에는 Crubit이 있다. 컴파일러 통합으로 인해 툴체인 의존적이지만, 그 대가로 거의 모든 것이 자동화되고 전체 API 표면이 별도 선언 없이 노출된다. 더 높은 ergonomics, 더 풍부한 타입 지원, 그러나 “소스에서 전체를 빌드”하는 워크플로우가 전제 조건이 된다. 어느 쪽이 맞는지는 프로젝트의 성격에 달려 있다. 소규모 임베디드 시스템과 대규모 클라우드 마이크로서비스가 같은 해법을 원하지 않는 것은 당연하다.


언어 의미론의 비대칭성: C++에서 Rust를 호출하는 것이 더 어렵다

Rust와 C++ interop이 양방향으로 동일한 난이도를 가질 것이라 예상하기 쉽지만, 실제는 극히 비대칭적이다. Rust에서 C++를 호출하는 것이 훨씬 어렵다. 이 비대칭성의 근본 원인은 언어 의미론의 표현력 차이에서 비롯된다.

강연자는 C++ 의미론이 Rust의 상위 집합에 가깝다고 설명한다. 함수 오버로딩, 연산자 오버로딩, 복잡한 상속 구조, 자기 참조 객체, memcpy 불가 타입 등 C++이 제공하는 의미론은 Rust에 직접 대응되는 개념이 없는 경우가 많다. 반면 Rust의 강력한 타입 시스템은 interop 도구들이 함수와 타입의 의도된 의미론을 비교적 명확하게 파악할 수 있게 해준다. Rust 컴파일러에 타입을 쿼리하면 크기, 정렬, 구조체 필드, Drop trait 구현 여부(= C++ 소멸자), Clone trait 구현 여부(= 복사 생성자) 등 명확한 정보를 얻을 수 있다. C++ 측에서는 이러한 쿼리 결과가 훨씬 모호하고, 원래 코드의 의도를 **추측(guesswork)**에 의존해야 하는 경우가 많다.

함수 오버로딩 문제

C++의 함수 오버로딩은 Rust interop에서 현실적으로 자주 마주치는 문제다. C++의 오버로드 집합(overload set) 중 특정 오버로드를 Rust에서 명확하게 지칭할 방법이 없기 때문이다. 일부에서는 이를 해결하기 위해 Rust 언어 자체에 오버로딩을 추가해야 한다고 주장하지만, 강연자는 이에 강하게 반대한다. 오버로딩을 추가하면 Rust의 강력한 타입 추론 능력을 심각하게 훼손한다는 것이다. 그는 ABI 레벨에서 오버로딩은 단순히 다르게 맹글링(mangling)된 심볼 이름에 불과하다는 점에 주목한다. 따라서 핵심 언어를 건드리지 않고 컴파일러와 링커 수준의 트릭으로 해결할 수 있을 가능성이 있다고 주장한다. Carbon 언어가 C++과의 최대 호환성을 위해 오버로딩을 적극 수용한 것과는 대조적인 철학이다.


ABI 안정성: 진짜 문제의 시작

강연에서 가장 많은 시간이 할애된 주제는 ABI(Application Binary Interface) 안정성이다. 강연자는 ABI가 프로그래밍 언어의 속성이 아니라 플랫폼과 툴체인의 속성임을 먼저 명확히 한다. 클래스 레이아웃, 필드 오프셋, vtable 구조, 호출 규약(calling convention), 이름 맹글링 등 모든 것이 ABI의 영역이다.

Rust는 명시적으로 “컴파일러 버전마다 ABI가 바뀔 수 있다”는 입장을 취한다. 이는 미래의 최적화 가능성을 열어두기 위한 의도적인 선택이지만, interop 입장에서는 큰 난관이다. C++ 세계에서도 COM에서 Swift 5에 이르기까지 ABI 안정성 문제는 수십 년째 논쟁 중이다. Swift 5는 library evolution과 ABI resilience를 동시에 달성한 근래의 성공 사례로, 강연자는 Doug Gregor의 관련 발표를 참고할 만한 자료로 언급한다.

강연자가 제안하는 방향은 구조체 레이아웃 메타데이터를 컴파일러가 자동으로 생성하고, 내부(Internal) ABI와 외부(External) ABI를 명시적으로 구분하는 것이다. 라이브러리 내부에서는 메타데이터 조회와 간접 참조의 비용을 피할 수 있고, 외부와의 계약 경계에서만 이 비용을 감수하는 구조다. 동적 라이브러리를 로드하는 시점에 이 메타데이터를 한 번만 조회해 캐시하면, 이후 디스패치 비용은 사소한 산술 연산 수준이 된다. 런타임에 필드 오프셋을 읽어 포인터 연산으로 실제 위치를 계산하고, 스택에 객체를 배치하려면 메타데이터에서 크기를 읽어 적절한 alloca 명령을 내리는 식이다.

꼬리 패딩 문제 (Tail Padding)

구조체 레이아웃에서 정렬 요구사항으로 인해 마지막 필드 이후에 패딩이 생기는 경우가 있다. C++은 파생 클래스의 필드를 기반 클래스의 꼬리 패딩 영역에 배치할 수 있고, 고유한 주소가 없는 필드의 꼬리 패딩을 인접 필드가 사용할 수도 있다.

꼬리 패딩 개념 설명 슬라이드

41:16 - 꼬리 패딩 개념 설명 슬라이드. 구조체 레이아웃에서 패딩 위치와 C++/Rust의 처리 방식 차이를 보여주는 다이어그램

이는 C++ 컴파일러가 메모리를 효율적으로 사용하기 위해 사용하는 합법적이지만 놀라운 최적화다. Rust는 이와 정반대로, 꼬리 패딩을 값의 일부로 취급한다. memcpy(sizeof(T))를 하면 그게 전부다. 이 근본적인 차이 때문에, Rust에서 C++ 파생 클래스 참조를 기반 클래스 참조로 변환하는 것이 불가능하다. 그렇게 하면 꼬리 패딩을 덮어쓰게 되어 C++ 객체의 유효한 데이터를 망가뜨릴 수 있기 때문이다.


Trivially Relocatable: C++26이 해결해야 할 문제

강연의 또 다른 핵심 주제는 trivially relocatable 타입 개념이다. 객체를 메모리의 임의 위치로 이동해도 불변식(invariant)이 유지되는 타입을 relocatable이라 부른다. C++의 기본 가정은 타입이 relocatable하지 않다는 것인데, 이는 interop 관점에서 매우 불편한 기본값이다.

Rust는 항상 memcpy로 값을 이동한다. 어떤 타입이든 바이트 복사로 이동하는 것이 합법적이다. 반면 C++은 이동 시 특수 함수(이동 생성자, 소멸자)를 호출해 포인터 픽스업(fix-up) 등을 처리하는 것이 기본이다. 자기 참조 구조체나 외부 리소스를 갖는 타입은 단순 복사로 이동할 수 없기 때문이다. Rust에서 이런 C++ 타입을 다룰 때 Pin<T> 같은 무거운 도구에 의존해야 하고, ergonomics가 심각하게 저하된다.

C++26에서는 trivially relocatable에 대한 공식적인 규격화 작업이 진행 중이다. 강연자는 이것이 단순한 편의 기능이 아니라 Rust/C++ interop의 핵심 전제 조건임을 강조한다. C++ 객체를 Rust 스택에 직접 배치하려면(힙 할당 없이 값 전달을 하려면) 해당 타입이 trivially relocatable함을 보장할 수 있어야 하기 때문이다. 이와 관련해 David Sankel의 상세 문서를 필독 자료로 권한다.

Trivially relocatable 타입의 성립 조건 목록 슬라이드

48:26 - Trivially relocatable 타입의 성립 조건 목록 슬라이드. C++26 제안의 계약 조건들이 나열되어 있음


컴파일러의 역할과 미래 방향

강연의 말미에서 Victor Ciura는 위에 언급된 모든 문제들이 결국 컴파일러 수준의 깊은 개입을 요구한다는 결론으로 수렴한다. 타입 레이아웃 메타데이터 자동 생성, 고신뢰도 의미론 매핑, ABI 리실리언스 제공 등 어느 것도 언어나 라이브러리 레벨에서만 해결되지 않는다.

C++ 측에서는 컴파일러 다양성(Clang, MSVC, GCC 등) 때문에 툴체인 독립적인 IR 표준에 대한 합의가 필요하다. 이는 아직 미해결 문제이며, 강연자는 이 분야가 “심각하게 투자 부족 상태”라고 지적한다. 그럼에도 불구하고 여러 기업과 컴파일러 전문가들 사이에서 관심이 높아지고 있다는 점은 고무적이다.

C++26의 리플렉션(Reflection) 기능도 미래의 중요한 가능성으로 언급된다. Herb Sutter의 CppCon 2025 키노트에서 소개된 C++26 리플렉션이 Rust뿐만 아니라 다양한 언어로의 바인딩 생성에서 강력한 도구가 될 수 있다는 전망이다. 강연자는 “몇 년 안에 C++ 자체가 새로운 덕테이프가 될 수도 있다”는 반농담 섞인 희망을 전한다.


단기·중기 로드맵

이상적인 북극성(North Star)을 향해 가면서도, 강연자는 현실적인 단계를 제시한다. 단기적으로는 CXX, Zengar, Crubit 같은 기존 interop 크레이트들에 지속적으로 기여해 완성도를 높이는 것이 중요하다. 이들 도구는 이미 많은 프로젝트에서 실사용 중이므로 단번에 버릴 수 없고, 실제 사용에서 축적된 피드백이 중장기 솔루션 설계에 귀중한 통찰을 제공하기 때문이다.

중기적으로는 Crubit이 시도하는 방향처럼 더 매끄러운 자동화 interop을 향한 접근법을 평가하고, 무엇보다 문제 공간 자체에 대한 공동 문서화가 필요하다는 점을 강조한다. 산업 전반에서 각기 다른 배경과 니즈를 가진 팀들이 논의에 참여했을 때, 서로가 실제로 합의하고 있는 영역이 어디인지 파악하는 것 자체가 수개월이 걸리는 작업이었다고 강연자는 회고한다. Rust Foundation이 C++ 표준화 과정에 공식 참여하기 시작한 것도 이런 맥락에서 의미 있는 변화다.

이 주제에 관심 있는 개발자라면 Zulip 서버의 전용 Interop 채널에서 이루어지는 스터디 그룹 논의에 참여할 수 있다. C++ 커뮤니티에서 익숙한 이름들도 다수 참여 중이며, 대부분의 미팅은 하이브리드 형태로 운영된다.


마무리: 고신뢰도 상호운용성은 아직 미완성이다

Victor Ciura는 강연을 시작할 때의 주장으로 돌아와 마무리한다. 수년간 많은 시도가 있었지만, 고신뢰도 Rust/C++ 상호운용성은 아직 달성되지 않았다. 각각의 솔루션은 저마다의 트레이드오프를 가지고 있으며, 프로젝트의 규모, 워크플로우, 요구사항에 따라 적합한 선택이 다르다. “그냥 작동하게 만드는 것”은 이미 가능하지만, 그것만으로는 충분하지 않다. 언어 의미론의 풍부한 매핑, 효율적인 값 전달, ABI 리실리언스, 동적 링킹 지원이 모두 갖추어진 해법을 향한 여정은 아직 진행 중이다.

이 문제는 한 도구, 한 라이브러리, 한 언어 기능의 문제가 아니다. 컴파일러, interop 라이브러리, 디버거, ABI, 빌드 시스템, 패키징, 링킹이 모두 맞물린 복합적인 도전이다. 그리고 그것을 해결하기 위해서는 기술뿐만 아니라 커뮤니티와 업계 전반의 협력이 필요하다.


📚 참고 자료 (강연 외 추가 정보)

아래 자료들은 강연자가 영상 내에서 직접 언급한 참고 자료들입니다. 강연 내용과 별개로 심화 학습에 유용합니다.

  • Doug Gregor의 Swift ABI Resilience 발표: Swift 5에서의 library evolution과 ABI resilience 달성 방법에 대한 상세 설명
  • David Sankel의 Trivially Relocatable 문서: C++ 객체의 trivially relocatable 조건과 Rust/C++ interop에서의 중요성을 다룬 심층 PDF
  • Zengar Rust/C++ Interop 발표 (David): Zengar의 설계 철학과 트레이드오프를 다루며 이미 온라인에서 시청 가능
  • Google Crubit 심층 발표: Crubit을 넘어 interop 전반의 도전 과제를 다룬 Google 엔지니어들의 발표 (CppCon 2025 기준 아직 온라인 미공개)
  • Google의 Rust/C++ 혼용 보안 연구: 전체 재작성 없이 점진적 Rust 도입만으로도 시스템 전반의 신뢰성이 향상됨을 데이터로 증명한 연구

📚 출처 및 참고

구분내용
원본 영상Rust/C++ Interop Challenges - Victor Ciura - CppCon 2025
채널CppCon
강연자Victor Ciura
행사CppCon 2025
리포트 작성2026-04-07
작성 방식YouTube 영상 STT 변환 후 에디팅

💬 본 리포트는 영상 내용을 바탕으로 재구성되었습니다. 정확한 내용은 원본 영상을 참조해 주세요.