Rust와 C++ 간 원활한 상호 운용성을 목표로 한 배경과 동기, 대안 조사, 설계 공리와 지향점, 언어/라이브러리/컴파일러/도구 측 통합 지점 제안, 언어 간 미스매치 사례, 그리고 2025-02-26 디자인 미팅 토론 기록을 정리합니다.
안전하지 않은 C/C++ 같은 언어에서 Rust와 같은 현대적이고 더 안전한 언어로 옮기려는 산업 전반의 움직임이 커지고 있습니다. 이는 Rust에게 큰 기회이며, Rust가 스스로 만들어낸 흐름이기도 합니다.
Rust에 대한 관심은 매우 강해서, 일부 분야에서는 도입이 거의 "피할 수 없는" 일처럼 느껴지기 시작했습니다. 하지만 다른 언어로 된 대규모 기존 코드베이스와 라이브러리가 있는 몇몇 핵심 영역에서는 도입을 가로막는 큰 장벽이 존재합니다. 이들 영역에서는 기존 언어를 유지하려는 현지적 인센티브가 어느 한 개발 팀(혹은 여러 팀이 함께해도)이 극복하기 어려울 만큼 강합니다. 이 영역에서 Rust가 성공하려면 도입이 가능해질 정도로 비용을 낮춰야 합니다.
이 문서에서 초점을 맞추는 언어는 C++입니다. Mozilla 시절 Rust가 Firefox에서 대체하려 했던 바로 그 언어죠. 보시다시피, Rust와 기존 시스템 언어 사이의 불일치는 오늘날의 상호 운용(interop) 솔루션에 상당한 "세금"을 부과합니다. 지금까지는 가장 단순한 C++ API를 제외하고는 바인딩을 만드는 것이 사실상 불가능했습니다. 이는 Swift처럼 처음부터 상호 운용을 염두에 둔 언어와 경쟁할 수 있을 만큼 견고한 상호 운용을 Rust가 제공할 수 있는지에 대한 믿을 만한 의구심을 낳았습니다.
Rust는 견고한 상호 운용을 제공할 능력이 있지만, 이를 달성하려면 프로젝트의 다양한 부분과 업계 파트너 간의 조율된 노력이 필요합니다. 동시에, Rust의 가치관과 일치하는 방식으로 이를 수행하는 것이 중요합니다. Rust를 특별하게 만드는 많은 기능은 표현력, 견고함, 안전성, 성능의 독특한 조합을 가능하게 합니다. 우리는 Rust가 별개의 언어로서의 지위를 유지하면서 경계를 넘어서는 확장 지점을 제공할 수 있는 설계 영역에 머무를 것을 제안합니다.
최근 몇 년간 소프트웨어 산업에서 Rust의 영향력은 과소평가하기 어려울 정도가 되었습니다. 특히 메모리 안전성은 유럽 연합과 미국 등 정부 기관의 주목을 받았고, 이들은 메모리 안전 언어의 도입을 가속화하기 시작했습니다. 두려움 없는 동시성은 동시 시스템에 대한 수요가 커지는 시기에 등장했습니다. 한편 개발자와 경영진 모두 Rust의 더 나은 도구, 생산성, 신뢰성과 같은 이점을 인정하고 있습니다.
Google이 Rust 도입을 경험하면서 두 가지 큰 교훈이 도드라졌습니다:
첫 번째 포인트는 매우 고무적입니다. Rust 도입은 즉시 가치를 드러낸다는 뜻이니까요. 두 번째 포인트는 우리에게 선택을 제기합니다. Rust가 모든 C/C++ 사용자가 현실적으로 도입할 수 있는 옵션이 되어야 할까요, 아니면 지금까지 성공을 거둔 영역에 머물러야 할까요?
블로그 글 Eliminating Memory Safety Vulnerabilities at the Source에서 발췌:
취약성은 지수적으로 감소합니다. 반감기가 있습니다. 2022년 Usenix Security에 발표된 대규모 취약성 수명 연구는 이 현상을 확인했습니다. 연구자들은 취약성의 대다수가 새 코드 또는 최근에 수정된 코드에 존재한다는 것을 발견했습니다:
이는 두 가지 중요한 결론으로 이어집니다:
문제는 압도적으로 새 코드에 있으므로, 근본적으로 코드를 개발하는 방식을 바꿔야 합니다.
코드는 시간이 지날수록 지수적으로 성숙해지고 더 안전해지며, 코드가 오래될수록 재작성 같은 투자에 대한 수익은 감소합니다.
예를 들어 평균 취약성 수명에 따르면, 5년 된 코드는 새 코드보다 취약성 밀도가 3.4배(해당 연구의 수명 기준)에서 7.4배(Android와 Chromium에서 관찰된 수명 기준) 낮습니다.
이 결과는 놀랍고도 고무적입니다. 오랫동안 코드를 안전하게 만드는 유일한 방법은 재작성이라는 믿음이 있었습니다. 실제로는, 블로그 글에서 말하듯 "인터롭은 새로운 리라이트"입니다. 언어 경계 지점을 만들고 새 코드는 메모리 안전 언어로만 작성하면, 때로는 막대한 재작성 비용 없이도 유사한 이점을 얻을 수 있습니다.
제 관찰로는 언어 도입의 진척도를 좌우하는 세 가지 주요 축이 있습니다: 사회적, 경제적, 기술적. 사회적은 개인 수준의 동기입니다. 경제적은 자원 간의 절충을 관리하는 것입니다. 기술적은 일정 기간과 자원 범위 내에서 가능한 것과 불가능한 것을 정의합니다.[1]
어떤 경우에는 한 축에서의 움직임이 다른 축의 장애물을 극복할 수 있습니다. 그러나 어떤 경우에는 합리적인 시간과 자원 제약 내에서 Rust 도입이 기술적으로 가능해 보이지 않는 한계에 부딪힙니다. 기술적으로 가능하더라도, 더 쉽게 사용할 수 있고 견고한 Rust 상호 운용 솔루션은 다른 절충을 Rust에 유리하게 크게 기울일 수 있습니다.
오늘날 Rust 도입이 가능한 상황은 다음과 같습니다:
이는 완전히 새로운 프로젝트는 물론, 사회적·경제적으로 전면 재작성이 가능한 기존 프로젝트의 완전 재작성을 포함합니다.
컴포넌트 간에 자연스러운 프로세스 간 경계가 있는 프로젝트는 Rust로 더 쉽게 마이그레이션할 수 있습니다. 경계가 강제하는 느슨한 결합 덕분에 프로젝트를 한 번에 한 컴포넌트씩 점진적으로 옮길 수 있습니다. RPC/HTTP 경계를 가진 마이크로서비스 아키텍처가 한 예입니다. 다만 마이크로서비스조차도 직렬화, 데이터베이스, 공유 비즈니스 로직을 위한 공통 라이브러리에서 이점을 얻습니다.
C ABI로 수동 표현할 수 있는 작고 단순한 API 표면을 가진 프로젝트. 이 경계는 unsafe 코드로 표현·호출되며, 사람의 실수에 취약합니다. 표면이 충분히 작으면 유지보수 가능하지만, 이는 Rust 도입이 오히려 언어 경계에서 안전성을 낮출 수도 있음을 의미합니다.
제한된 API 어휘(기본 타입과 언어 기능만)를 가진 프로젝트는 bindgen, cbindgen, cxx 같은 기존 상호 운용 도구를 사용할 수 있습니다.
위의 옵션들이 존재하고 활발히 개발되고 있다는 사실은 개발자들이 Rust 도입에서 가치를 본다는 증거입니다. 그러나 이 옵션들은 오늘날 생산 환경의 상당 부분을 놓치고 있습니다. 즉, C++ 같은 언어의 API를 풍부하게 활용하면서, Rust의 상호 운용 지원이 상대적으로 제한적이고, 재작성이 불가능할 정도로 많은 코드를 링크하는 프로젝트들입니다.
여기에는 다음과 같은 영역이 포함됩니다:
이들 영역 대부분에서 소규모·취미 Rust 프로젝트가 존재하고 심지어 생산 환경 사용도 있습니다. 관련 블로그 글을 볼 때마다 흥분됩니다! 하지만 이런 것들은 유지 비용이 큰 라이브러리나 바인딩에 의존하며, 이런 비용은 높은 동기를 가진 개인이나 프로젝트의 존재론적 필요 때문에만 감수됩니다.
규모 있는 생산 환경으로의 전환은 여전히 요원합니다. 이를 위해서는 수많은 라이브러리 유지관리자와 사용자들의 거대한 조율된 노력이 필요하지만, 누구도 독자적으로 행동할 권한이 충분하지 않습니다.
더 나아가, 현재 상호 운용 도구의 한계는 기존 도구에 기능을 추가하는 문제만은 아닙니다. 많은 한계는 다양한 축에서 두 언어의 표현력 불일치에서 비롯됩니다. 예를 들어 C++과 Java는 오버로딩을 지원하지만 Rust는 지원하지 않습니다. 어떤 경우에는 이 불일치를 Rust가 언젠가 추가할 누락된 기능으로 널리 받아들입니다. 다른 경우에는 Rust의 표현력 부족이 그 자체로 장점으로 간주되기도 하지만, 그럼에도 상호 운용을 더 어렵게 만듭니다.
한 예로, Rust 컴파일러 자체가 손수 관리되는 바인딩 계층을 통해 여러 LLVM C++ API와 인터페이스합니다. 이 계층을 책임지는 소규모 워킹 그룹이 있어 다행입니다. 그렇지 않았다면 Rust는 존재하지 않았을 것입니다! 그럼에도 불구하고, 템플릿, 상속, CRTP 같은 풍부한 C++ 패턴 사용 때문에 Rust가 MLIR의 이점을 얻거나 LLVM의 고도로 커스터마이즈 가능한 패스 인프라스트럭처를 많이 노출하는 일은 아마 없을 것입니다.
풍부한 C++ 상호 운용에서 가장 유망한 두 언어는 Swift와(신흥) Carbon입니다. 기타와 오늘날 주요 Rust 솔루션의 범위는 부록을 참조하세요.
Swift는 Apple이 Objective C 기반 개발 생태계를 더 현대적이고 안전한 언어로 진화시키기 위해 만든 메모리 안전 언어입니다. 이 양방향 상호 운용은 Clang을 라이브러리로 Swift 컴파일러에 통합함으로써 작동합니다. 이후 Google 엔지니어들의 도움으로 Swift는 C++ 상호 운용 지원을 추가했습니다. Swift는 이제 Objective C와 C++ 모두에 대해 양방향 상호 운용을 제공합니다.
Objective C 상호 운용에는 최종 API를 깔끔하게 다듬기 위한 견해가 담긴 재작성 규칙들이 수반되었습니다.
언어 기능, 상호 운용 기능, 두 언어를 이해하는 컴파일러의 수직 통합
수직 통합된 단일 상호 운용 솔루션에 집중함으로써 빠르게 결과물을 냈지만, 실험의 여지는 제한됩니다.
함수 시그니처만으로 함수가 무엇을 할지에 대한 "합리적 가정"을 하려 합니다. 이를 바탕으로 일부 함수는 unsafe로 표시합니다.
바인딩을 사용자 정의하기 위해 C++ 소스 애너테이션에 의존합니다.
Rust의 fn foo(&self) -> &Field와 같은 패턴을 표현하기 위해 borrowing 같은 함수 한정자를 언어에 추가했습니다. 물론 Rust가 여기서는 더 표현력이 높습니다.
Carbon은 Google이 시작한 C++의 후속 언어입니다. 기술적으로는 Swift가 ObjC에 대해 했던 것을 C++에 대해 하려 합니다. 즉, 양방향 상호 운용을 핵심 설계 제약으로 하는 현대적 언어입니다.
Carbon은 실험적이며 대대적 개발이 진행 중입니다. 아직 생산 환경 준비가 되지 않았습니다. 2025년에는 두 가지 중점 분야가 있습니다:
Swift와 마찬가지로, Carbon은 풍부한 상호 운용 지원을 제공하기 위해 Clang을 컴파일러에 통합합니다. 설계 목표에는 다음이 포함됩니다:
C++ 코드를 Carbon으로 자동 변환
사용 중인 거의 모든 C++ 의미를 언어로 매핑하려 하되, 쉽게 마이그레이션 가능하거나 중요하지 않은 경우에는 호환성을 버리기
거버넌스를 Rust를 본떠 설계
메모리 안전을 핵심 기능으로
이것은 선택지가 매우 많은 프랙탈적 설계 공간입니다. 모든 사용 사례에 대해 정답을 낼 수는 없습니다. Swift/ObjC는 Apple의 API 표면에 집중함으로써 성공했습니다. cxx는 Meta의 특정 요구(이는 Google과 Microsoft의 요구와 다릅니다)에 초점을 맞추어 성공했습니다.
따라서 우리는 "가능하게 하는" 접근을 취해야 합니다. 즉, 공통 문제를 해결하도록 언어를 확장하고, 우리가 편한 확장 지점의 집합을 정의하는 것입니다.
이는 양방향(러스트→C++ 호출, C++→러스트 호출)에서 "자동 바인딩 불가능"한 API를 "가능"으로 더 많이 옮기는 것을 목표로 해야 합니다. 더 많은 API를 안전하고 인체공학적으로 바인딩하는 데에도 도움이 될 수 있지만, 그 대부분은 특정 상호 운용 솔루션, 특정 코드베이스에 맞춘 커스터마이즈, 그리고 C++ 자체에서 표현할 수 있는 것에 달려 있습니다.
다양한 사용 사례에 필요한 노력의 양. 크기는 오늘날 각 범주에 속하는 사용 사례의 수를 나타냅니다. 목표는 이 피라미드를 뒤집는 것입니다.
사용한 만큼만 비용을 치른다: C++를 사용하지 않는 사람들에게 Rust를 더 나쁜 언어로 만들지 말자.
외부 개념을 표현할 때 적합하다면 가능한 한 네이티브 Rust 메커니즘을 사용하자. 정당한 이유가 없다면 표현력 장벽을 제거하기 위해 언어를 확장하자.
Rust를 모든 언어의 합집합으로 만들지 말자: Rust에서 정의할 수 없는 인터페이스를 호출할 수 있도록 잘 정의된 확장 지점을 만들자.
extern "C"의 가변 인자(varargs)생태계를 한 번에 한 컴포넌트씩 진화시킬 수 있도록 양방향 상호 운용을 지원하자.
경계를 자동화하고 검사하자. 기계가 대신할 수 있는 위험한 일을 사람이 수동으로 해야 할 이유가 없어야 한다.
이는 점근적인 목표입니다. 극도로 풍부하고 다양하며 때로는 기묘하기까지 한 C++ 언어의 표면적을 고려할 때, 이 지향점들은 결코 100% 달성되지도, 되어서도 안 됩니다. 그럼에도 불구하고, 이 지향점이 수용된다면 방향성을 제공하며, 오늘보다 훨씬 더 그 방향으로 나아가고자 하는 합의를 뜻합니다.
이는 가상의 Rust/C++ 상호 운용 도구인 krusty 사용자 설명서에서 가져온 예시입니다.
cargo add krusty
cargo add --build krusty-build
// main.rs
krusty::include!("<vector>") as cpp_std;
krusty::include!("mylib.h") as mylib;
struct Item(i32);
struct MyJob {
work_items: cpp_std::vector<Item>,
}
impl MyJob {
fn new() -> MyJob {
let mut work_items = cpp_std::vector::new();
for i in 1..=100 {
work_items.push_back(Item(i));
}
MyJob { work_items }
}
}
impl mylib::Job for MyJob {
fn size(&self) -> usize {
self.work_items.size()
}
fn make_progress(&mut self) {
// ...
}
}
fn main() {
mylib::WorkerPool::new().submit(MyJob::new());
mylib::SpecializedWorkerPool::new().submit(MyJob::new());
}
cpp// mylib.h class WorkerPool { public: WorkerPool(); virtual ~WorkerPool(); void submit(Job job); void submit(std::function<void()>); }; // 가상 인터페이스의 예. struct Job { virtual size_t size() const = 0; virtual void make_progress() = 0; }; // 템플릿 인터페이스의 예. template<typename J> class SpecializedWorkerPool { public: SpecializedWorkerPool() { /* ... */ } virtual ~SpecializedWorkerPool() { /* ... */ } void submit(J job); };
이 예시에서 우리는 다음을 봅니다:
WorkerPool::submit() 호출표시되지 않은 많은 것들:
우리는 아직 어떤 Rust 기반 솔루션에서도 이 수준의 역량에 한참 못 미치지만, 장기적으로는 이런 기능의 상당수가 광범위한 도입에 중요할 것으로 예상합니다. 따라서 가능한 것에 대해서는 "원칙적으로" 수용하는 합의를 얻고, 넘지 말아야 할 "굵은 레드라인"이 있다면 무엇인지 알고 싶습니다.
특히:
마지막으로, 언어 팀은 검토에 앞서 이러한 제안을 반복할 소위원회를 원할 수 있습니다. 이는 다른 업계 참여자들(다른 회사, Carbon과 Swift도 관심이 있다면 등)을 참여시키기에도 좋은 자리일 것입니다. 충분한 관심을 모을 수 있다면, 이 작업을 Rust 프로젝트 내에서 호스팅하는 데 수용성이 있을까요?
@cImport/@cInclude로 C 헤더 파일을 직접 가져오기 허용Meta의 양방향 상호 운용 솔루션
API의 양쪽을 ffi 모듈에서 중복 정의해야 합니다
unsafe를 추가할 수 있고, 추가하지 않으면 안전한 것으로 간주됩니다중복된 API가 원본과 일치하는지 컴파일 타임 검증 사용
각 표준 라이브러리에서 가장 흔한 타입들에 대한 내장 지원: 문자열, 슬라이스, 벡터, 박스/유니크 포인터, Result
세 가지 종류의 타입: 불투명 Rust 타입, 불투명 C++ 타입, 공유 타입
그 외 기능은 제한적: 예를 들어 커스텀 템플릿은 없음
C++ 및 잠재적으로 다른 언어와의 풍부한 상호 운용을 가능하게 하는 데 필요한 주요 통합 지점과, 이를 위해 우리가 할 수 있는 큰 변화의 예를 나열합니다. 이는 기정사실화가 아니라 추가 대화를 위한 출발점임을 유의하세요.
Crubit에는 사용 중이거나 사용하고자 하는 기존 불안정 기능 목록도 있습니다.
Vec 같은 매우 흔한 라이브러리 타입의 안정적 레이아웃
Location::file_with_nul 같은 상호 운용 친화적 인터페이스
시그니처와 레이아웃 정보 같은 Rust API 정보를 내보내는 능력
상호 운용 플러그인 API
Rust와 C++의 상이한 진화는 상호 운용 솔루션이 해결해야 할 꽤 많은 주름들을 만들어냈습니다. 어떤 것들은 임박하게 해결 가능하지만, 다른 것들은 풀어내는 데 수년이 걸릴 수도 있습니다.
상호 운용을 우선순위로 삼고(바라건대 C와 C++ 표준 위원회와 협력함으로써), 이런 새 주름들이 다려지는 속도보다 더 빠르게 나타나는 일을 방지할 수 있습니다.
#[repr(C)] 구조체의 drop 순서는 C++와 반대이므로, 구조체의 drop 순서를 커스터마이즈할 방법이 필요합니다.
C++ 포인터와 참조는 자유롭게 가변 별칭을 만들 수 있지만, Rust 참조는 그렇지 않습니다.
타입에 [[no_unique_address]]가 있는 기존 C++ 객체에 Rust 코드가 대입(assign)하는 것은 불가능합니다
no_unique_address 필드로의 memcpy, memmove는 UB(C/C++에서도 마찬가지)ZST(크기 0 타입): C++에는 없으며, 댕글링 포인터에 대한 어떤 연산도 UB입니다
타입 기반 별칭 분석(Strict aliasing): Rust는 이에 호환되지 않습니다. 규칙상 잘못된 타입으로 포인터를 읽기/쓰기할 수 없지만, Rust에서는 이 방식으로 트랜스뮤트할 수 있습니다. C++의 추상 기계에는 "진짜 동적 타입"이 있습니다.
음수 오프셋을 가진 가상 기본 클래스의 출처(provenance): Rust의 출처 규칙을 여기까지 확장할 수 있을까요? 불확실합니다.
이동(move) 시맨틱스: C++ 객체는 "moved-from" 상태가 있어야 하지만, 모든 Rust 객체는 자명하게 재배치 가능(trivially relocatable)해야 합니다.
Pinning: C++에는 비자명 재배치 타입에 대한 인체공학적 대입이 있으며, 이는 Rust에서는 pinning으로 표현해야 합니다.
쓸 수 없는 꼬리 패딩: Rust는 꼬리 패딩을 값의 일부로 취급하며, 사용자들은 예를 들어 mem::swap을 size_of::<T>() 바이트의 memcpy로 구현할 수 있기를 기대합니다. C++는 이를 허용하지 않습니다. 파생 클래스의 필드가 기본 클래스의 꼬리 패딩에 배치될 수 있기 때문입니다.
Jon Bauman: 저는 재단에서 C++ 이니셔티브와 관련된 일을 하고 있습니다(지난 8개월).
Dmytro: Google의 관련 노력을 이끌고 있습니다. 이전에는 Swift를 조사했고, Apple에서 해당 분야에서 일했습니다.
Devin Jeanpierre: 저는 Google에서 Dmytro와 함께 일하며, Crubit의 기술 리드를 맡고 있습니다.
Jon Bauman: 많은 사람과 이야기해 본 결과, 예전보다 C++ 위원회 프로세스에 대해 더 낙관적입니다. Rust가 해온 일에 대한 존중이 큽니다. 오스트리아에서 "프로파일"이 긍정적인 반응을 얻었지만, 앞으로 나아가고 있지는 않습니다.
Josh: 우리는 C++ 쪽에서 아무 일도 일어나지 않을 것처럼 진행해야 하며, 만약 무언가 일어나면 우리에게 반가운 놀라움이 될 것입니다.
TC: Jon, Google의 작업과 어떻게 연결된다고 보시나요? 그리고 더 넓게는 C++ 위원회와의 상호작용과 어떻게 연결되나요?
Jon: 장기적으로 crubit 접근이 유망해 보입니다. 이것이 Google 제품 그 이상이 되려면, 더 친화적이고 개방적으로 만드는 작업이 더 필요할 겁니다. 하지만 재단의 일은 승자를 고르는 것이 아니라 연결을 구축하는 것이라고 봅니다.
위원회 프로세스에서 Rust에 대한 적대감은 예상했던 것만큼 느껴지지 않습니다. 모든 구현자들이 있습니다.
Josh: 위원회의 방향은 컴파일러 구현자들의 움직임과 상충하는 것처럼 보입니다.
Jon: 위원회에서 구현자들이 나서서 어떤 것이 그들에게는 작동하지 않는다고 말하면, 그것이 사실상 거부권에 가장 가깝습니다.
Josh: 아마 저는 그 반대를 말하려던 것 같지만, 나중에 논의하죠.
NM: 설계 공리에 대해 이야기해야 할 것 같습니다. 또한 이것이 언어 팀의 사안인지 프로젝트 전반의 사안인지에 대한 질문도 있습니다. 제 생각에는 언어 팀 내에서 추진할 수 있는 일은 추진하되, 상호 운용을 더 글로벌한 초점으로 삼는 것에 대해서도 이야기해야 합니다. RfL 작업에서 보았듯이, 성공을 보여줄 수 있다면 다른 이들도 참여하게 될 것입니다.
nikomatsakis: 저는 이것이 일어나길 바랍니다. 또한 우리가 먼저 다룰 문제를 식별했으면 합니다. 범위가 넓으니, "우리가 가장 먼저 해야 할 일"이 무엇인지 감을 잡고 싶습니다. 언어 팀 내부에서만 할 수 있는 일과 cargo 등 더 넓은 프로젝트 참여가 필요한 일을 구분해 논의하는 것도 좋겠습니다. 먼저 소규모로 할 수 있는 것들을 고르는 게 좋겠습니다.
cramertj: 한 초점 지점은 Pin 인체공학일 수 있습니다. Pin은 C++ 타입을 다룰 때 자주 관련됩니다. Pin에 대한 많은 개선은 CppRef(일명 AliasRef) 같은 것에도 도움이 될 것입니다.
cramertj: 또 다른 소규모 초점 지점은 FFI 상호 운용 API(예: CString/CStr 개선)와 일반적인 Rust API의 레이아웃 보장(예: 슬라이스 표현 보장 RFC)입니다.
cramertj: (여담) cargo와 관련해서, 오늘 C++을 사용하는 사람들은 코드를 cargo로 빌드하지 않을 것입니다. 따라서 어떤 솔루션이든 기존의 다언어 빌드 시스템(Make, CMake, Bazel, Buck) 내에서 작동해야 합니다.
NM: 즉, 당장 할 수 있는 것들이 있습니다. 그다음엔 무엇을 할까요?
tmandry: 언어 팀 작업으로 시작할 수 있다는 점에 동의하지만, 나중에는 더 넓은 정렬이 필요합니다. 언어 팀이 흥미를 느끼는 방향으로 밀고, 그 다음 컴파일러와 표준 라이브러리를 이야기할 수 있습니다. 하지만 우리가 그런 것을 원치 않는다면 이 모든 대화는 무의미합니다.
cramertj: 동의합니다. 더 넓은 프로젝트 노력입니다. 여기서 바라는 것은 일부 우선순위에 관한 문제를 푸는 것입니다. C++ 상호 운용을 어렵거나 불sound하게 만드는 자잘한 문제의 롱테일이 있습니다. 일부는 고칠 수 있습니다. 일부는 근본적이라 언어에 더 큰 변화를 하지 않는 한 우회 API를 만들어야 합니다. 더 큰 일들도 고려할 수 있습니다. 예를 들어 많은 상호 운용 도구는 다양한 타입의 크기와 정렬 등을 이해하고 싶어 합니다. 지금 Google에서는 바인딩을 생성하기 위해 rustc_driver를 실행합니다. 하지만 더 안정적인 무언가를 만들 수도 있습니다. 저는 사실 이 부분에 약간 회의적입니다. 그러나 우리가 성장하고 성숙해 가면서 언어 바인딩 생성기가 Rust 타입에 대해 어떤 정보가 필요한지 더 명확해질 것입니다.
NM: 제가 관심 있는 것은 기대하는 영향과 효과입니다.
전반적 반응: 상호 운용은 매우 중요하고 C++는 핵심 타깃입니다. 다른 언어들과(러스트 전도단은 사양!) 덜 대립적인 관계를 구축하고 싶습니다. 세상에는 코드가 넘쳐나므로, C++ 위원회뿐 아니라 우리와 친밀한 다른 언어들(Swift, Go 등)과도 긴밀히 협력하여 양방향 상호 운용을 우선순위로 두는 아이디어가 좋습니다.
큰 비전 문서에 흥분되지만, 이 문서와 baumann의 문서는 모두 "어디서 시작할까!"라는 의문을 남깁니다. 할 일이 너무 많습니다! 그래서 저는 일종의 "첫 목표"를 정하고 싶습니다. 많은 사람들이 Rust를 쓰고 싶지만 공급업체가 제공한 C++ API 등을 다뤄야 하는 상황에서 C++ API를 소비하는 것을 가능하게 만들고, 우리가 새로운 컴파일러 패턴을 확립해야 하는 멋진 C++ 기능은 제한했으면 합니다. 예컨대 템플릿 인스턴스화와 모노모픽화의 상호작용을 살펴보고, 그 공간에서 end-to-end를 스케치합시다.
libkrusty::include 같은 건 멋져 보였습니다. =)
(발언 후 TL;DR: &Cstr 같은 쉬운 것들을 처리하는 동시에, 달성 가능하면서도 모호성을 최대한 줄여주는 야심찬 목표를 하나 고르자.)
+1, 기술적으로나 커뮤니티적으로 다른 언어와 공존할 수 있는 긍정적 관계를 조성하는 것은 매우 가치 있습니다. 이미 Rust로 Python과 JavaScript 패키지를 구축하는 데 성공을 보았습니다.
C++ 상호 운용은 일괄 전환을 감당할 수 없고(그리고 순수 Rust 하위 라이브러리를 가능케 하는 C-ABI "컷 포인트"가 분명치 않은) 대규모 프로젝트에 특히 가치가 큽니다.
이 문서에서 충분히 논의되지 않은 중요한 포인트는 빌드 시스템 상호작용입니다. C++ 프로젝트는(처음에는) cargo를 사용하지 않으며, cargo로는 대체할 수 없는 복잡한 다언어 빌드 시스템(Make, CMake, Bazel, Buck)을 자주 사용합니다.
저는 큰 그림이 보여서 기쁩니다. Rust의 타 언어와의 상호 운용이 매우 매끄럽고 빠르게 발전하여 Carbon 같은 프로젝트가 불필요해지기를 바랍니다.
Zig가 libclang을 직접 활용하여 일종의 "더 나은 C 컴파일러"가 되려 한 사용자 경험은 영감을 줬습니다. 물론 우리가 그대로 하려는 건 아닙니다.
우리가 거기에 이를 수 있을까요? 모르겠습니다. 하지만 잠시 크게 생각해 보며 무엇을 해낼 수 있을지 보는 것은 가치가 있습니다. 문서에 설명되었듯이, Rust는 지금 정말 "그 순간"을 맞이하고 있는 것 같습니다.
동시에, Rust가 무엇인지를 보호해야 합니다. 이를 위해 Rust를 본질과 다른 것으로 만들 수는 없습니다. Rust는 고유의 정체성을 지켜야 합니다. 하지만 이 균형을 잡을 수 있다고 봅니다.
우리는 전에, 다른 이유로, 보다 "플러그인 가능"하거나 "훅 가능"한 언어와 컴파일러를 지원하는 방법에 대해 얘기했습니다. 아마 이런 것이 여기에서도 역할을 할 수 있지 않을까 합니다. 즉, 언어를 일반적으로 더 혁신적인 방식으로 강력하게 만드는 과정에서 상호 운용이 자연스럽게 따라올 수도 있습니다.
제가 문서를 썼기 때문에 편향이 조금 있을지도 모르지만, 상호 운용은 Rust의 성장과 발전에 중요한 단계라고 생각합니다. Rust의 생존 여부 차원이 아니라 — 우리는 그 단계는 한참 지났습니다 — 얼마나 번영하고 장기적으로 어떤 큰 영향을 미칠 수 있느냐의 차원에서요.
프로젝트 전반에 걸쳐 협업이 필요하다고 봅니다. Niko가 말한, 야심차지만 잘 범위가 한정된 "첫 번째 큰 목표" 아이디어가 마음에 듭니다. 동시에, 세부 기능의 우선순위는 사용 사례에 의해 더 많이 좌우될 것이라고 봅니다.
상호 운용은 분명 중요합니다. 저는 세부사항과 컴파일러 기여진에 얼마나 범위 확장이 될지에 대해 걱정됩니다. 가장 특이한 C++의 일부를 여전히 다소 못생기게 남겨두는 80%+ 해법을 찾을 수 있으리라 낙관합니다. 그 정도면 괜찮다고 볼 수 있을 것입니다.
예를 들어 러스트만 사용하는 코드베이스에서 C++ 컴파일러 전체를 추가로 항상 다운로드하지 않아도 되도록, 별도의 rustup 컴포넌트로 만들 수 있을지 궁금합니다. "사용한 만큼만 비용" 원칙의 확장입니다.
C와 C++ 상호 운용에 대해 엄청난
입니다.
일반 공리에 동의합니다. 특히 상호 운용 기능을 사용하지 않는 Rust 사용자에게 불이익이 없어야 한다는 점에요. 그리고 다른 사람의 코드에서 복잡한 언어 기능을 마주치는 것도 복잡성 비용입니다. 어떤 것이 그 기능을 사용하지 않는 사람에게 영향을 주지 않는다고 말하는 것만으로 충분치 않습니다. 사람들은 쓰는 것보다 훨씬 더 많은 코드를 읽기 때문입니다. 또한 주의할 점: 상호 운용 기능은 상호 운용에만 사용되지 않을 것입니다. 우리가 추가하는 어떤 기능도 우리가 예상치 못한 용도를 포함해 어디에나 사용될 수 있습니다.
하지만 이를 감안하더라도, 현실 세계 C++ 코드베이스의 98%와 상호 운용하는 데 필요한 모든 기능을 추가할 수 있습니다(이는 C++ 사양의 98%라는 뜻이 아닙니다). 비용 대비 이익이 너무 낮은 기능들(비규범적 예: C++ RTTI 같은 것)은 과감히 거절할 준비를 해야 합니다. 그러나 예를 들어 저는 상속과의 완전한 상호 운용(러스트에서 C++를 상속, C++에서 러스트를 상속)을 보고 싶습니다.
C보다는 C++에서 더 멀리 나아가야 한다고 생각합니다. C의 경우 저는 rustc에 양방향 상호 운용에 대한 완전한 네이티브 지원이 있기를 바랍니다. C++의 경우 외부 도구/크레이트/매크로가 필요해도 괜찮다고 생각합니다. 다만 rust-lang 조직에서 "공식" 도구/크레이트/매크로를 유지하는 것을 보고 싶습니다.
대규모 코드베이스(또는 그 일부)의 재작성은 중요하다고 봅니다. 그리고 상호 운용의 거대한 목표 중 하나는 코드베이스를 모듈별로 점진적으로 Rust로 재작성할 수 있게 하는 것입니다. 이는 많은 사용 사례 중 하나입니다. 다른 예로는 C/C++로 작성된 "엔진"을 통합하거나 시스템 라이브러리를 호출하거나, 시스템 라이브러리를 작성하는 것이 있습니다.
이 이니셔티브의 성공을 측정하는 방법으로, 일반적인 C 또는 C++ 코드베이스에서 출발해 일부 Rust를 추가하고 기존 코드와 상호 호출하게 만드는 데 걸리는 시간을 제안합니다. C의 경우 5분 이내가 되었으면 합니다. C++의 경우 대략 30분 이내를 제안합니다.
상호 운용 공리에 전반적으로 긍정적입니다. 특히 Wasm Components 관점에서 보았을 때, 우리 또한 더 나은 Rust 통합에 매우 관심이 있습니다. Rust/C++ 통합 개선을 위한 어떤 노력도 Rust와 다른 타깃 간의 통합에 도움이 될 것이라 생각합니다.
대규모로 Rust를 도입하려면 좋은 C++ 상호 운용이 정말 중요합니다. 40년 된 C 코드베이스를 재작성할 이유는 거의 없습니다. "Rust를 모든 언어의 합집합으로 만들지 말자"라는 설계 공리가 특히 마음에 듭니다.
libkrust::include! 예시는 정말 멋져 보였습니다!
libclang을 rustc에 내장해야 하는 접근에는 회의적이지만, 동일한 일을 하는 프로시저 매크로나 유사한 것이라면 크게 개의치 않습니다.
Niko: 제가 추가하고 싶은 또 다른 공리는 "언어 상호 운용은 확장성 기능이다"입니다. 저는 Rust가 다른 언어에 대해 직접 알 필요는 없지만, 그래도 목표에 도달할 수 있기를 바랍니다. 모두가 여기에 동의하는지는 모르겠습니다. C와 C++의 차이는 무엇일까요?
TC: 동의합니다. 우선 언어를 더 강력하게 만들 수 있는지 살펴보고, 상호 운용 같은 우리가 원하는 다른 것들이 자연스럽게 따라오는지 보아야 합니다. 이는 Josh가 제기한 언어 표면적 측면에서의 복잡성 최소화 우려에도 도움이 된다고 생각합니다.
cramertj: Rust에서 현재 크레이트에 정의된 RustType으로 std::vector<RustType>을 인스턴스화하는 예에 집중하고 싶습니다. 이는 정말 어렵습니다. 러스트와 C++ 컴파일러 로직 사이에서 타입 레이아웃 정보를 공유하고, 몇 번의 왕복이 필요하기 때문입니다. 이를 지원한다는 것은 Clang과 rustc 사이에 우리가 원하지 않을 수 있는 더 깊은 상호 운용을 함의합니다. Swift와 Carbon은 Clang을 링크하기 때문에 이를 제공할 수 있습니다.
Connor: C와 C++의 차이는 C++가 훨씬 더 많은 작업을 요한다는 점입니다.
eholk: std::vector<RustType> 예시에 +1입니다. C++ 상호 운용이 일반적인 언어 간 상호 운용의 특수 사례였으면 좋겠습니다. 예를 들어 미래에 Go 상호 운용을 원한다면, rustc에 Go 컴파일러를 포함하지 않아도 되면 좋겠습니다. 이를 위해 rustc의 통합 지점이 어떻게 생겨야 할지는 모르겠지만, 아마 이 요구사항들은 서로 상충할 수도 있습니다.
scottmcm: Niko가 말한 것이 옳게 들립니다. 이를 언어 기능으로 추상화하면 좋겠습니다. 이를 vector 예시와 어떻게 조화시킬지는 모르겠습니다. 아마 특정 것들을 특수 처리할 가치가 있을지도요. 사람들이 다른 C++ 컴파일러를 얼마나 신경 쓰는지도 모르겠습니다.
tmandry: 실제로 사람들은 MSVC를 사용합니다. cramertj가 말한 vector 인스턴스화 예시는 좋은 예입니다. 이는 우리가 필요로 하는 아키텍처 차이를 가리킵니다. 예를 들어 컴파일러-플러그인 API가 있다면(많은 작업이 필요하다는 점은 인정), 그걸로 결합을 낮출 수 있고, Niko가 말한 방향과도 일치합니다.
Josh: 저는 vector의 정확한 사용 사례가 작동해야 한다고 동의합니다. 유사한 것들도요. 이를 작동시키는 방법은 여러 가지를 상상할 수 있습니다. 우리가 거기까지 갈 수 없다면, C++ 지원을 잘하고 있지 못한 것입니다. 그리고 우리는 그걸 잘해야 합니다.
cramertj: 생각보다 훨씬 어렵습니다, 그 정도까지 가는 것도.
NM: 그 예시가 제가 제안하는 첫 마일스톤입니다. 그리고 Rust가 Clang을 알 필요는 없다고 봅니다. 콜백을 추가하면 됩니다. 아마 어렵겠죠. 하지만 가능한 한 열심히 해결해 보아야 합니다. 불가능하다고는 생각치 않습니다. 그로부터 많은 것을 얻을 것입니다.
NM: Josh의 C에 대한 포인트는 모두 말이 됩니다. 하지만 Rust가 C 헤더를 파싱해야 한다고는 생각하지 않습니다. 그건 제가 선을 긋고 싶은 지점입니다. 우리가 그 헤더를 파싱하고 C 매크로를 처리할까요, 아니면 다른 이들이 그렇게 할 수 있도록 도구를 제공할까요? 제게는 후자가 옳아 보입니다.
cramertj: 그렇게 해도 비슷한 곳에 도달합니다. 그 플러그인들은 컴파일러가 하는 모든 일을 할 수 있어야 합니다. 예를 들어 const 본문을 평가할 수 있어야 하고, 여러 번 앞뒤로 왕복해야 합니다. 따라서 프로세스 내에 있어야 할 것입니다.
NM: 그 버전을 안정화하는 것은 어렵습니다. 하지만 사용 가능하기 위해 즉시 안정적일 필요는 없습니다. 아마 공진화할 수 있을 겁니다. clippy가 현재 rustc에 불안정하게 연결하는 대신 이 API를 사용할 수 있도록 하는 것은 이 작업을 위한 유용한 목표가 될 것입니다.
tmandry: +1.
Jon: 우리는 복잡성 없이 모든 것을 원한다는 것을 배웠습니다.
Jon: 예를 하나 들면, 외부 시그니처에 대해 특정한 것을 증명할 수 있다면 그 함수는 안전하다고 증명할 수 있습니다. 하지만 심볼이 안전하다고 증명할 수는 없습니다. 시그니처가 올바르다는 것을 가정해야 하기 때문입니다. 이런 것은 컴퓨터가 해야 하는 일처럼 보입니다.
scottmcm: 사람은 그렇게 하면 안 된다는 것에 동의합니다. 하지만 컴퓨터가 unsafe 선언을 작성하는 것은 문제가 없어 보입니다.
NM: +1.
우리가 아직 어떤 Rust 기반 솔루션에서도 이 수준의 역량에 못 미치지만, 장기적으로는 이런 기능의 상당수가 광범위한 도입에 중요할 것으로 예상합니다. 따라서 가능한 것에 대해서는 "원칙적으로" 수용하는 합의를 얻고, 넘지 말아야 할 "굵은 레드라인"이 있다면 무엇인지 알고 싶습니다.
특히:
상호 운용을 새로운 언어 기능의 정당화 근거로 삼을 수 있을까요?
C++와의 상호 운용 경계에서 Rust에 없는 의미를 표현할 수 있을까요?
상호 운용이 어느 정도까지만 좋아도 된다는 상한선이 있을까요? Rust와 C++ 사이에 다른 언어가 자리할 여지를 남겨야 할까요?
마지막으로, 언어 팀은 검토에 앞서 이러한 제안을 반복할 소위원회를 원할 수 있습니다. 이는 다른 업계 참여자들(다른 회사, Carbon과 Swift도 관심이 있다면 등)을 참여시키기에도 좋은 자리일 것입니다. 충분한 관심을 모을 수 있다면, 이 작업을 Rust 프로젝트 내에서 호스팅하는 데 수용성이 있을까요?
tmandry: 답하고 싶습니다.
- 상호 운용을 새로운 언어 기능의 정당화 근거로 삼을 수 있을까요?
가능합니다
- C++와의 상호 운용 경계에서 Rust에 없는 의미를 표현할 수 있을까요?
가능합니다
- 상호 운용이 어느 정도까지만 좋아도 된다는 상한선이 있을까요? Rust와 C++ 사이에 다른 언어가 자리할 여지를 남겨야 할까요?
아니오. 하지만 각 기능은 그 자체의 장단점으로 평가합니다.
TC: 또한 특정 기능이 아니라, 전반적으로 그 기능을 어떻게 지원할지 논의해야 합니다.
Niko: 그리고 첫 마일스톤을 더 명확히 정의하고 싶습니다.
scottmcm: 또한 "얼마나 멀리가 너무 먼가"도 논의하고 싶습니다. 예: 암시적 move-constructible 타입들.
(회의는 여기서 종료되었습니다.)
설계 공리
사용한 만큼만 비용을 치른다: C++를 사용하지 않는 사람들에게 Rust를 더 나쁜 언어로 만들지 말자.
- 이 논의에서, 우리는 "기능이 더 많아지는 것"을 비용으로 간주하지 않습니다. 그 기능이 일상적인 Rust 사용자에게 직접 영향을 주지 않는 한.
외부 개념을 표현할 때 적합하다면 가능한 한 네이티브 Rust 메커니즘을 사용하자. 정당한 이유가 없다면 표현력 장벽을 제거하기 위해 언어를 확장하자.
- 예: 고유(자체) 연관 타입, 커스텀 리보로우/오토레프 타입
Rust를 모든 언어의 합집합으로 만들지 말자: Rust에서 정의할 수 없는 인터페이스를 호출할 수 있도록 잘 정의된 확장 지점을 만들자.
- 선례:
extern "C"의 가변 인자(varargs)생태계를 한 번에 한 컴포넌트씩 진화시킬 수 있도록 양방향 상호 운용을 지원하자.
경계를 자동화하고 검사하자. 기계가 대신할 수 있는 위험한 일을 사람이 수동으로 해야 할 이유가 없어야 한다.
지향점(North star)
모든 C++ API를 unsafe Rust에서 호출 가능하게 하자.
모든 Rust API를 C++에서 호출 가능하게 하자.
가능한 많은 경우에 바인딩을 안전하게 만들자.
다른 언어의 API를 사용할 때 필요한 노력은 작업의 크기에 비례해야 한다.
다른 언어의 사용자를 수용할 때 드는 유지보수 부담은 사용자 수에 비례하고, 소스 언어의 사용자와 유사해야 한다.
이는 점근적인 목표입니다. 극도로 풍부하고 다양하며 때로는 기묘하기까지 한 C++ 언어의 표면적을 고려할 때, 이 지향점들은 결코 100% 달성되지도, 되어서도 안 됩니다. 그럼에도 불구하고, 이 지향점이 수용된다면 방향성을 제공하며, 오늘보다 훨씬 더 그 방향으로 나아가고자 하는 합의를 뜻합니다.
Rust 컴파일러 자체가 손수 관리되는 바인딩 계층을 통해 여러 LLVM C++ API와 인터페이스한다
scottmcm: https://github.com/llvm/llvm-project/tree/main/llvm/include/llvm-c는 업스트림에 있으며, Rust가 LLVM을 호출하는 방식의 대부분을 다룹니다. rustc만 C++가 아닌 코드베이스가 LLVM을 사용하고 싶어 하는 것이 아니기 때문입니다.
tmandry: 존재하긴 하지만, Rust(그리고 아마 대다수의 생산 사용자)가 필요로 하는 전체 표면을 다루지는 않습니다.
scottmcm: 우리가 최근 3번 새로운 LLVM 기능을 사용하기 시작했을 때를 보면
모두 LLVM-C API가 있었습니다. 그중 하나에 C++ 코드가 있는 이유는 여러 버전의 LLVM을 지원하고 싶기 때문입니다.
scottmcm: 그래서 제 관찰은, "완전한" C++ 상호 운용의 방향성은 특히 "내부"에 관한 것이고, 여러 언어에서 소비되기를 원하는 라이브러리에 관한 것은 덜하다는 것입니다. 그런 라이브러리는 어차피 C++ 표현의 ABI가 아닌 것을 노출하고 싶어 할 것입니다.
[[no_unique_address]]에 관하여Connor: [[no_unique_address]](명확히 말하면 필드에 적용됨)는 Rust 쪽에서는 대체로 ZST로 표현될 수 있습니다. 그 효과는(일반적으로 C++에서는 1바이트 패딩을 갖는) 빈 타입을 컨테이너 내에서 0바이트로 저장할 수 있게 해주기 때문입니다.
MSVC는 이 동작을 구현하지 않기 때문에, ZST 사용은 완전히 이식 가능하지는 않습니다. 그러나 Itanium(gnu 및 비-MSVC clang) 타깃 코드를 대상으로 할 때, [[no_unique_address]]는 Rust의 ZST와 일치하는 의미를 가집니다.
tmandry: 또한 타입의 패딩 바이트를 다른 필드에 재사용할 수 있게 해줍니다.
Devin: [[no_unique_address]]는 최적화할 수 있는 타입의 측면에서 ZST보다 훨씬 더 강력합니다. 예를 들어 Bar라는 타입을 생각해 보세요: struct Foo {int64_t x; int32_t y;}; struct Bar {[[no_unique_address]] Foo foo; int32_t z;};. Bar는 struct Bar2 {int64_t x; int32_t y; int32_t z;};와 동일한 레이아웃을 가질 수 있습니다. (우연히도 예: Clang/Linux에서는 사소한 이유로 — Foo가 "POD for layout" — 그렇진 않지만, 요지는 전달됩니다.) 반대로, ZST는 [[no_unique_address]]가 (쉽게) 할 수 없는 일을 할 수 있습니다. 예를 들어 정확한 오프셋을 제어(repr(C) 사용)하거나, 동일 타입의 ZST 필드를 여러 개 같은 주소에 둘 수 있습니다. 둘은 일부 지점에서만 정확히 일치하며, 그것도 우연입니다.
Josh: 이 문서는 몇몇 곳에서 C++를 넘어선 더 일반적인 지침/정책(예: 임의의 다른 언어와의 상호 운용)을 가정합니다. 다른 언어에 대한 선례를 반드시 세우지 않고도 C++ 상호 운용을 검토·고려해야 한다고 생각합니다. C와 C++ 상호 운용은 다른 언어에 비해 Rust에 더 중요하고 더 높은 프로파일을 갖는다고 관찰하는 것은 절대적으로 타당합니다. Rust는 C와 C++ 그리고 극히 소수의 다른 언어만이 잘 작동할 수 있는 공간에서 활동하고 있으며, C와 C++ 상호 운용을 지원하기 위해 우리가 승인하는 범위가, Rust와 C와 C++이 고유하게 잘 작동하는 공간과 덜 밀접하게 맞닿은 다른 인기 언어들을 위한 상호 운용에 대해 승인할 범위보다 더 넓을 수 있습니다.
다시 말해: C++ 상호 운용을 하는 데에는
이고, 다른 언어에 대한 광범위한 정책 설정은 건너뛰자고 제안합니다. 다른 언어에 대해서는 언어별로 결정을 내릴 수 있습니다. (또는 미래에는, 안전한 언어의 광범위한 집합과 상호 운용하는 방법을 정의할 수 있기를 바랍니다.)
nikomatsakis: 더 광범위한 버전에 대해 어떤 우려가 있나요? 제 주요 우려는 문을 너무 넓게 열면 집중력을 잃을 수 있다는 것입니다. 일반적으로는 가능한 한 상호 운용을 지원해야 한다고 생각합니다. 논의 중인 많은 기능(특히 컴파일러 플러그인 포인트)은 다른 언어에도 유용할 것입니다. 또한 그 설계가 확장 가능한지 처음부터 확실히 하고 싶습니다.
Josh: C와 C++ 외의 다른 언어에 대해 규범적 정책을 만드는 것을 피하고 싶습니다. C와 C++ 상호 운용을 하는 데 다른 언어에 대한 일반 규범적 정책이 필요하지 않기 때문입니다.
Josh: 이 문서는 C++ 상호 운용에 대해 광범위하게 이야기합니다. 저는 C와의 상호 운용이 동일하거나 그 이상으로 중요하다고 생각합니다. 특히 C의 경우 C++보다 더 멀리 나아갈 준비가 되어 있습니다.
구체적으로, 현실 세계 C++ 코드의 98%와 상호 운용하고(예: 많은 C++ 코드베이스가 거부하는 RTTI 같은 일부를 제외할 수 있고), 외부 도구/크레이트/매크로 등이 필요할 수 있습니다. C에 대해서는 rustc만으로 100% 상호 운용을 원하며, C++에 대해서는 그만큼 멀리 가지 않아도 됩니다.
cramertj: 참고로, 저는 C++와 98% 상호 운용을 기대하지 않습니다 — 그 수치는 훨씬 낮다고 봅니다. 복잡한 제네릭 패턴과는 실질적으로 상호 운용하기 어렵습니다.
Josh: 정정: 사양의 98%가 아니라, 현실 세계 코드의 98%입니다. ;)
cramertj: 저는 현실 세계 코드를 말하고 있습니다. STL과 다른 표준 라이브러리들(예: absl)은 Rust 쪽에서 지원할 수 없는 고급 템플릿 메타프로그래밍을 광범위하게 사용합니다.
Josh: "충분히 진화된 도구"라면 가능할지도요. Clang/LLVM 위에 구축된 상호 운용/바인딩 도구가 제네릭과 템플릿 메타프로그래밍을 임의로 처리할 수 있다고 상상할 수 있습니다. 예컨대 Rust 쪽에서 사용할 인스턴스화를 미리 선언해야 할 수도 있고, 반대 방향 상호 운용을 위한 C++ 헤더 생성에 도움이 되는 애너테이션을 작성해야 할 수도 있습니다.
cramertj: 원래 요점으로 돌아가면, C 상호 운용이 C++ 상호 운용보다 훨씬 더 달성 가능하다는 데 동의합니다. 네이티브 지원이 더 많아지면 좋겠습니다.
Josh: 
cramertj: 다음 질문은 어떤 C 컴파일러와 전처리기를 번들링할 것인가입니다 :). Clang이 가장 명백한 선택이며, Clang을 C++ 컴파일러로 사용하는 사람들에게 C++ 바인딩 생성에도 도움이 되겠지만, MSVC 지원은 "복잡"합니다.
nikomatsakis: C에 +1이지만, 왜 rustc에 C 상호 운용이 다른 것보다 더 내장되어야 하는지 명확하지 않습니다. 그 포인트를 설명해 주실 수 있나요?
cramertj: 저는 C 지원을 네이티브로 번들링할 이유보다 C++ 지원을 네이티브로 번들링할 이유가 더 많다고 생각합니다 — C 바인딩은 bindgen 같은 도구로 사전 생성하기 훨씬 더 쉽습니다. Josh, 어떤 걸 염두에 두셨나요? (자명한 경우는 "인체공학"일 테지만, 가능한 경우 외부 도구를 선호합니다. 예: 더 미니멀한 std)
Josh: 이건 더 긴 대화이고, 꼭 이 디자인 미팅에서 시간을 써야 하는 주제는 아닙니다. 별도로 대화하고, 이 디자인 미팅이 다루고자 했던 모든 주제를 놓치지 않도록 합시다.
그럼에도, 질문에 답하자면:
static inline 함수의 완전 지원, 헤더 파일을 "네이티브"로 포함, 매크로를 나쁘지 않게 처리, 빌드 시스템의 CFLAGS를 별도의 도구에 다시 가르치지 않아도 되는 정도를 뜻합니다…#use rust_module을 하고 Rust 함수를 호출하고 Rust 타입을 인스턴스화할 수 있기를 바랍니다. 효과적으로 C를 "네이티브" Rust 호환성으로 확장하는 것입니다. 그리고 Option, Result 같은 타입을 이해하도록 가르치는 것입니다.하지만 이 모든 건 다른 미팅에서 이야기합시다.
Location::file_with_nul 등Connor: 특히 C++에서 std::string_view와 std::u8string_view를 활용할 수 있는지 보고 싶습니다. 이는 C FFI 지원으로 API를 오염시키지 않고도 러스티한 슬라이스 타입을 사용할 수 있게 해줍니다(이는 C++ 상호 운용에 반드시 필요한 것은 아닙니다).
cramertj: 문제는 FILE이나 std::source_location과 함께 작동하는 기존 C 및 C++ API와의 상호 운용입니다. 이들은 널 종료 문자열을 사용합니다.
scottmcm: 여기서 특히 강조하고 싶은 부분은, 모든 사람을 위해 유효성 불변식과 레이아웃 최적화를 약화시키지 않는 것입니다.
cramertj: 네, 절대적으로 동의합니다 — C++ 상호 운용 때문에 순수 Rust 코드의 안전성이나 성능을 악화시키고 싶지 않습니다.
cramertj: 적어도 우리가 원하는 것 중 일부(예: 쓸 수 없는 꼬리 패딩)는 Rust보다 C++에서 더 효율적인 동작을 허용하는 영역입니다. 예: 오늘날 Rust의 구조체와 튜플은 다른 값의 꼬리 패딩에 데이터를 저장할 수 있다면 훨씬 더 작아질 수 있습니다. 다만 기존 unsafe 코드 때문에 이를 허용하는 것은 어렵습니다.
Devin: 별칭(aliase) 시맨틱스는 "사용한 만큼만 비용" 원칙을 적용하기 어려운 곳일 수 있습니다. 예를 들어 -fno-strict-aliasing의 Rust 등가물을 추가해 C++/Rust 혼합 코드베이스에서 UB 없이 가변 별칭을 허용하고 싶을 수 있습니다. (하지만 그렇게 원하는 이유는 Rust 생태계의 많은 부분이 별칭을 금지하는 참조 타입에 의존하기 때문입니다. 다른 길은 커스텀 참조 타입을 사용하기 쉽게 만드는 것입니다.)
cramertj: 만약 상호 운용을 할 때 noalias/dereferenceable을 비활성화한다면, 그것은 C++ 상호 운용을 하는 프로젝트에만 비활성화하는 합리적인 일로 보입니다. 따라서 순수 Rust 코드는 비악화되지 않을 것입니다.
scottmcm: 부록의 다음 항목은 "사용자 정의 참조 타입"입니다. 이는 사용자 정의 self 타입과 구별되는, 무엇을 의미하나요? place 통합이 있는 것, 리보로잉이 있는 것 … ?
cramertj: 이는 CppRef(일명 AliasRef) 같은 타입입니다. Rust의 dereferenceable이나 noalias 요구사항을 갖지 않습니다. 구체적으로, C++ 함수를 별도의 추가 별칭 제한을 호출자에게 부과하지 않고 Rust API에 바인딩하고 싶습니다.
변경을 가하는 C++ 메서드를 단순히 &mut self Rust 메서드로 번역하면 우리가 피하고 싶은 별칭 가정을 하게 됩니다.
scottmcm: 이해했습니다. 다만 CppRef<'a, T>가 사용자 정의 self 기능 외에 "사용자 정의 참조 타입"이 되기 위해 무엇을 원하는지 모르겠습니다 — Index 지원 등을 생각하는 건가요, 아니면…
cramertj: 언어 기능 측면에서는, 자동 참조(오토레프)를 원합니다(이전 Pin 타입에 대한 오토레프 논의 참조). 그리고 아마도 #[fundamental]이어서, 사람들이 &T에 대해 할 수 있듯이 CppRef<'_, MyType>에 대해 트레이트를 구현할 수 있기를 원합니다.

nikomatsakis: 높은 차원에서, 저는 이 문서를 사랑합니다. 프로젝트의 우선순위로 상호 운용을 채택하는 데 열정적으로 찬성합니다(언어 팀이 프로젝트 전부에 영향력을 갖는 것은 아니지만, 우리의 역할은 해야겠지요). 설계 공리에 동의합니다. 일반적으로 저는 이 같은 작업을 Rust 자체(언어, 컴파일러, cargo 등)와 생태계의 크레이트 간의 공동 노력으로 봅니다. 저는 상호 운용 크레이트를 많은 "DSL 유사" 프로시저 매크로 중 하나로 생각하는 경향이 있으며, 진정한 초점은 Rust를 확장 가능한 언어 및 컴파일러 아키텍처로 만드는 데 있다고 봅니다.
tmandry: 
scottmcm: "얼마나 이상하면 너무 이상한가"라는 주제에서, 포인터를 고쳐주는(move 시) 이동 생성자를 가진 연결 리스트 같은 것에 대해 이야기할 가치가 있을까요? 기대치는 이러한 타입들이 Rust에서 직접 사용 가능하고, Rust의 move가 그런 이동 생성자를 호출하는 것일까요, 아니면 그런 타입들은 Rust에서 "이동 불가"라고 하고, 특별한 함수(또는 무언가)를 사용해 그러한 동작을 유발해야 한다고 말해도 될까요?
cramertj: 오늘 우리는 이동 생성자를 Pin<&mut Self> 참조에서 동작하는 함수로 바인딩합니다. 여기서 더 인체공학적인 경로(예: 이런 타입들에 대한 대입 연산자 오버로드)가 있으면 좋겠지만, 반드시 필요하진 않습니다.
tmandry: 개인적으로 저는 C++에서 rvalue가 아닌 경우 std::move()를 사용해야 하듯이, .move() 같은 것을 써야 한다면 괜찮다고 봅니다. 보이지 않게 만들 얘기를 시작하기 전에, 이런 패턴을 더 인체공학적으로 표현할 수 있는 일종의 배치(emplacement) 기능부터 시작해야 한다고 봅니다.
yosh: Rust for Linux도 주소 안정 생성자와 관련된 문제가 있습니다: The safe pinned initialization problem. 이는 관련 있어 보이며, 여기 더 넓은 해법의 필요를 가리킬 수 있습니다.
Devin: Crubit에는 Rust for Linux와 유사한 것이 있습니다(moveit 기반): support/ctor.rs. 인체공학은 아쉬운 부분이 많습니다. 예를 들어 지역 변수에 move-construct하는 문법은 ctor::emplace!{let mylocal = ctor::mov!(x)}이고, 이는 mylocal : Pin<&mut X> 결과를 내며, 숨겨진 지역 변수를 핀(pin)합니다.
Devin: (참고로, Pin이 있어도 Pin::set의 존재는 비자명 이동 시맨틱스와 함께 사용하기 어렵게 만듭니다. 이런 타입들의 값이 Rust에서 어떤 공개 API에도 등장하지 않도록 철저히 금지해야 합니다.)
jon: 몇 주 전 오스트리아에서 열린 회의에서, 위원회는 C++26을 위한 Safety Profiles 추진에 반대표를 던졌습니다. 이전 회의에서 위원회 내 스터디 그룹은 Sean Baxter의 SafeC++(일명 Circle) 제안보다 Safety Profiles를 추구하기로 표결한 바 있습니다. 현재 위원회에 대한 제 관점은, Safety Profiles가 업계와 정부가 강조한 메모리 안전 문제를 해결할 수 없을 가능성이 높다는 것을 인정하지만, C++의 향후 방향은 불분명하다는 것입니다. SafeC++ 작업에는 잠재력이 있다고 생각하지만, Sean은 NVIDIA에서 관련 없는 일을 하게 되면서 이를 주도할 수 없게 되었습니다. 그렇긴 해도, 위원회 프로세스를 탐색하는 기술은 작업을 구현하는 기술과 다르기에 Sean이 반드시 적임자였다고 보지는 않습니다. 저는 C++과 모든 언어 간 상호 운용 개선에 초점을 맞춘 프로세스를 위원회 내에서 시작하는 데 관여하고 있지만, 당연히 Rust에 초점을 두고 있습니다. 회의 기간 동안 많은 사람들과 대화하며 느낀 점은, Rust가 보여준 개선 방향으로 C++를 향상시키는 데 대한 식욕은 크지만, Rust나 그 작동 방식에 대한 이해는 상대적으로 부족하다는 것입니다. 두 커뮤니티 간에는 거버넌스와 발전 프로세스가 매우 다르게 작동함에도 협업의 진정한 잠재력이 있다고 봅니다. 관계 구축은 이미 시작되었으며, 앞으로 Rust 프로젝트 구성원의 더 많은 참여가 도움이 될 수 있다고 생각합니다.
여기서 "기술적"이라는 표현은 가능한 것에 대해 이야기하는 특정한 의미로 사용하고 있음을 유의하세요. 이는 품질이나 개발자에게 더 즐거운 것에 관한 것이 아닙니다. 중요하긴 하지만, 그런 것들은 각각 이 프레임워크의 경제적, 사회적 범주에 속합니다.↩︎
bindgen#380, bindgen#607, bindgen#652, bindgen#778, bindgen#1194. 목록 출처: https://cxx.rs/context.html#c-vs-c.↩︎