왜 하필 Rust를 C로?
요즘 '메모리 안전성' 이야기에는 Rust가 빠지지 않죠. 리눅스 커널에도 들어갔고, 여러 보안 기관이 C/C++ 대신 메모리 안전한 언어를 쓰라고 권고하고 있어요. 그런데 현실은 그렇게 간단하지 않아요. Rust 툴체인을 들일 수 없는 프로젝트가 아직 많거든요. 오래된 임베디드 보드라서 C 컴파일러밖에 없는 경우도 있고, 빌드 시스템에 Rust를 추가하는 걸 팀이 꺼리는 경우도 있어요. 안전 인증을 받은 C 컴파일러만 써야 하는 산업도 있고요.
Eurydice는 바로 이 틈을 겨냥한 도구예요. Rust 코드를 사람이 읽고 리뷰할 수 있는 C 코드로 바꿔주는 컴파일러예요. 여기서 핵심은 '읽을 수 있다(readable)'는 점이에요. 기계가 대충 뱉어낸 C가 아니라, 원래 Rust 코드의 구조와 이름이 살아 있는 C를 만드는 게 목표예요.
어떻게 동작하나요?
전체 과정은 세 단계로 나눠 보면 이해하기 쉬워요.
1. Charon으로 Rust 코드 꺼내기: rustc 컴파일러는 내부적으로 MIR이라는 중간 표현을 만들어요. MIR이 뭐냐면, Rust 코드에서 문법적인 꾸밈을 걷어내고 '실제로 무슨 일이 일어나는지'만 남긴 단순한 형태예요. Charon이라는 도구가 이걸 꺼내서 분석하기 좋은 형식으로 정리해줘요.
2. Eurydice가 변환하기: Rust에만 있는 개념들을 C로 옮길 수 있는 형태로 하나씩 풀어요.
3. KaRaMeL로 C 출력하기: 마지막 C 코드 생성은 KaRaMeL이라는 기존 도구를 재활용해요. KaRaMeL은 원래 F라는 검증 언어로 작성된 암호 라이브러리 HACL를 C로 뽑아내는 데 쓰였어요. 그렇게 만든 코드가 Firefox와 CPython의 해시 구현 등에 들어가 있죠. 검증된 코드를 읽기 좋은 C로 내보내는 일에서는 이미 실전 경험이 많은 도구예요.
변환하면서 Rust의 개념은 대략 이렇게 바뀌어요.
- 제네릭: C에는 제네릭이 없어요. 그래서 실제로 쓰인 타입마다 함수를 따로 만들어요. 이걸 단형화(monomorphization)라고 하는데,
Vec<u8>용과Vec<u32>용 함수가 각각 생기는 식이에요. - 슬라이스(
&[T]): 포인터와 길이를 묶은 구조체가 돼요. Option,Result, enum: 어떤 종류인지 나타내는 태그와 데이터를 함께 담은 구조체가 되고,match는switch나if문이 돼요.- 소유권과 빌림: Rust가 컴파일할 때 이미 검사를 마쳤기 때문에, C에서는 평범한 포인터로 바뀌어요.
- mrustc: C++로 작성된 Rust 컴파일러로, C 코드를 출력해요. 다만 Rust 컴파일러를 처음부터 빌드하는 부트스트래핑이 목적이라 출력물은 사람이 읽기 어려워요.
- rustc_codegen_gcc / gccrs: GCC를 이용해 LLVM이 지원하지 않는 아키텍처에서도 Rust를 컴파일하려는 프로젝트예요. C를 거치지 않고 바로 기계어를 만들어요.
- c2rust: 반대 방향으로, C를 Rust로 바꿔요.
어떤 모양이 되는지 감을 잡을 수 있게 예시를 하나 들어볼게요. 실제 출력을 그대로 옮긴 게 아니라 이해를 돕기 위한 개념적인 예시예요.
// Rust
fn sum(xs: &[u32]) -> u32 {
let mut acc = 0u32;
for i in 0..xs.len() { acc = acc.wrapping_add(xs[i]); }
acc
}
// 변환된 C (개념적 예시)
uint32_t sum(Eurydice_slice xs) {
uint32_t acc = 0U;
for (size_t i = 0U; i < xs.len; i++) {
acc = acc + ((uint32_t *)xs.ptr)[i];
}
return acc;
}
함수 이름, 변수 이름, 루프 구조가 그대로 남아 있어서 C 개발자가 리뷰하기에 부담이 적어요.
실제로 어디에 쓰였나
대표적인 사례는 양자내성암호(PQC)예요. libcrux라는 라이브러리가 있는데, 양자 컴퓨터가 나와도 안전하다고 평가받는 ML-KEM 같은 새 암호 알고리즘을 Rust로 구현하고 형식 검증까지 마쳤어요. 이걸 C로 된 기존 암호 라이브러리에 넣으려고 Eurydice로 C 코드를 만든 거죠. 형식 검증이 뭐냐면, 테스트 몇 개 돌려보는 수준이 아니에요. '이 코드는 범위 밖 메모리에 절대 접근하지 않는다', '명세와 똑같이 동작한다'를 수학적으로 증명하는 거예요. 증명은 Rust에서 하고, 배포는 C로 하는 전략이에요.
비슷한 시도들과 비교
dyn Trait), 복잡한 클로저, 표준 라이브러리를 폭넓게 쓰는 코드는 변환이 어렵거나 손을 봐야 할 수 있어요.한국 개발자에게 주는 시사점
임베디드, 자동차 전장, 국방처럼 C가 사실상 표준인 분야에서 일하신다면 눈여겨볼 만해요. 핵심 로직은 Rust로 안전하게 짜고 납품은 C로 하는 하이브리드 전략이 가능해지거든요. 국내 금융·공공 분야의 검증필 암호모듈(KCMVP)도 대부분 C 기반이라, 앞으로 PQC로 넘어가는 과정에서 이런 방식이 참고가 될 수 있어요.
주의할 점도 있어요. 생성된 C 코드를 사람이 직접 고치기 시작하면 Rust에서 얻은 안전성 보장은 그 순간 사라져요. 원본은 언제나 Rust로 두고, C는 빌드 결과물로만 다루는 원칙이 꼭 필요해요. 처음부터 실무에 도입하기보다는 해시, 파서, 압축처럼 작고 순수한 계산 모듈로 먼저 실험해보는 걸 추천해요.
마무리
한줄 정리: Eurydice는 Rust를 쓸 수 없는 곳에도 Rust의 안전성을 보내주는 다리예요. 증명은 Rust에서, 배포는 C로.
여러분 팀에서 Rust 도입을 망설이는 이유는 뭔가요? 툴체인이 문제라면 이런 Rust → C 방식이 답이 될 수 있을까요, 아니면 결국 Rust를 직접 도입하는 정공법이 낫다고 보시나요?
🔗 출처: Hacker News