- Rust 프로그램을 Coq로 옮기는
coq-of-rust가 표준 라이브러리의 core·alloc 크레이트까지 다루기 시작하면서, primitive 함수별 Coq 정의를 손으로 작성하던 부담을 줄임 - 두 크레이트는 unsafe와 고급 Rust 코드가 많은 대형 코드베이스라, 자동 번역 결과를 컴파일·검증 가능한 단위로 다루는 것이 핵심 과제가 됨
- 출력물을 입력 Rust 파일 단위로 나누자
alloc은 54개 파일 171,783줄,core는 190개 파일 592,065줄이 됐고, 병렬 컴파일과 디버깅이 쉬워짐 impl블록의 모듈 이름 충돌은where절 정보를 포함해 완화했지만, 현재 Coq에서 컴파일되지 않는 파일은 전체의 4%로 남아 있음Option::unwrap_or_default예시는 자동 번역 정의와 단순 함수형 정의의 동등성을 증명해 쓰는 방식으로, 자동화 신뢰와 증명 시점 검사가 함께 필요함
Rust 표준 라이브러리 primitive 처리
- Formal Land는 Rust 프로그램을 Coq 형식 증명 시스템으로 번역하는
coq-of-rust를 개발 중임 - 기존에는 Rust 표준 라이브러리의 primitive 구성요소를 다루기 위해 함수마다 동작을 나타내는 Coq 정의를 별도로 만들어야 했음
- 예:
Option::unwrap_or_default - 수작업 정의는 반복적이고 오류가 끼어들기 쉬움
- 예:
- 이 부담을 줄이기 위해 Rust의
core와alloc크레이트를coq-of-rust로 번역함 - 번역 결과는 다음 경로에서 확인할 수 있음
초기 번역 실행 결과
coq-of-rust를alloc과core에 처음 실행하자, 각 크레이트 전체에 해당하는 수십만 줄 규모의 Coq 파일 2개가 생성됨- 큰 코드베이스에서도 도구가 실행된다는 점은 확인됐지만, 생성된 Coq 코드는 바로 컴파일되지 않았음
- 오류는 드물게 나타났지만 몇천 줄마다 하나 정도 발생하는 수준이었음
cloc기준 입력 Rust 코드 규모는 다음과 같음alloc: Rust 코드 26,299줄core: Rust 코드 54,192줄
- 번역 과정에서 매크로 확장이 일어나므로 실제 번역 대상은 원본 줄 수보다 더 큼
생성된 Coq 코드 분할
- 가장 큰 변경은
coq-of-rust출력물을 입력 Rust 파일 하나당 Coq 파일 하나로 나눈 것임 - 이 분할은 번역이 정의 순서에 둔감하고 context-free이기 때문에 가능했음
- Rust 파일 사이에는 보통 순환 의존성이 있고 Coq에서는 이를 허용하지 않지만, 이 번역 방식에서는 파일 단위 분리가 가능함
- 분할 후 출력 규모는 다음과 같음
alloc: Coq 파일 54개, Coq 코드 171,783줄core: Coq 파일 190개, Coq 코드 592,065줄
- 파일을 나누면서 생성 코드 탐색과 유지보수가 쉬워짐
- 컴파일을 병렬화하기 쉬움
- 한 파일씩 집중해 디버깅할 수 있음
- 컴파일되지 않는 파일을 제외하기 쉬움
- 단일 파일 diff를 추적하기 쉬워짐
모듈 이름 충돌 수정과 남은 파일
- 일부 버그는
impl블록의 모듈 이름 충돌에서 발생했음 - 해결 방식은 모듈 이름에 더 많은 정보를 넣어 고유성을 높이는 것이었음
- 기존에는 빠져 있던
where절 정보를 포함함 - 예를 들어
Mapping<K, V>에 대한Defaulttrait 구현에서는K와V가 모두Default를 구현해야 한다는 조건이 모듈 이름에 반영됨
- 기존에는 빠져 있던
- 현재 Coq에서 컴파일되지 않는 파일은 다음과 같음
alloc/boxed.vcore/any.vcore/array/mod.vcore/cmp/bytewise.vcore/error.vcore/escape.vcore/iter/adapters/flatten.vcore/net/ip_addr.v
- 이는 전체 파일의 4% 에 해당함
- 컴파일되는 파일 안에도 아직 처리하지 못한 Rust 구성요소가 공리화되어 있어, 이 비율만으로 미지원 범위 전체를 판단하기는 어려움
Option::unwrap_or_default 번역 예시
- Rust의
Option::unwrap_or_default는Some(x)이면x를 반환하고,None이면T::default()를 호출함 coq-of-rust는 이를 monadic 형태의 Coq 정의로 번역함- 입력 인자와 타입을 매칭함
Some분기에서는 tuple field를 가져와 복사함None분기에서는core::default::Defaulttrait의default메서드를 호출함
- 실제 검증에서는 자동 생성 정의 대신 더 단순한 함수형 정의를 사용함
None이면core.simulations.default.Default.default를 반환함Some x이면x를 반환함
- 자동 생성 정의와 단순 정의가 동등하다는 증명은
CoqOfRust/core/proofs/option.v에 있음 - 원본 Rust 코드가 바뀌면 이 증명을 통해 변화가 포착됨
core라이브러리 번역은 자동으로 이뤄졌기 때문에 손으로 작성한 정의보다 생성 정의를 더 신뢰할 수 있음- 다만
coq-of-rust에도 실수나 불완전성이 있을 수 있어, 증명 시점에 코드가 타당한지 확인해야 함
남은 과제
- Rust 프로그램 검증에서 표준 라이브러리 형식화에 대한 신뢰가 높아짐
- 다음 목표는 여전히 지루한 증명 과정 단순화임
- 특히 시뮬레이션이 원본 Rust 코드와 동등함을 보이려면 다음 작업이 필요함
- 이름 해석
- 고수준 타입 도입
- 부작용 제거
- 이 단계들을 나눠 다루는 것이 앞으로의 개선 방향임