잘 된 것들
- 재작성은 작은 단계로 이뤄졌고(점진적, stop-and-go), 잘 동작하며, 새 코드는 읽고 이해하기 쉬워졌음
- 모든 코드를 보는 시야를 가짐으로써 성능 최적화 기회를 얻음
- 1/3 ~ 1/2 정도의 사용하지 않는 코드를 제거함. 러스트나 Go 같은 현대 프로그래밍 언어는 데드 코드를 더 잘 찾아내고 개발자에게 알려줌
- 범위 밖 접근이나 오버플로우/언더플로우 걱정이 없음
- 내장된 테스트 프레임워크가 매우 유용함
- CMake 파일을 제거할 수 있어 기쁨
잘 되지 않은 것들
여전히 정의되지 않은 행동을 추적해야 함
- C/C++에서 러스트로 점진적 재작성을 하면서 많은 원시 포인터와
unsafe{}블록을 써야 했음 - 러스트 규칙이
unsafe내에서도 적용되지만 컴파일러가 체크하지 않으므로 정의되지 않은 행동이 쉽게 발생함 unsafe내에서는 여러 읽기 전용 포인터 XOR 한 개의 변경 가능 포인터 규칙을 쉽게 깰 수 있음- Miri가 이를 잡아주는 구원자 역할을 함
Miri가 항상 작동하지는 않고 여전히 Valgrind를 사용해야 함
- 암호화 라이브러리처럼 C나 어셈블리로 작성된 부분이 있는 라이브러리를 사용하면 Miri가 작동하지 않음
- Miri로 체크되지 않는 많은
unsafe코드가 있음 - 일부 테스트는
valgrind에서 실행해야 했음
여전히 메모리 누수를 추적해야 함
- C API의 일반적인 패턴은
MYLIB_init()에서 메모리를 할당하고MYLIB_release()에서 해제하는 것인데,MYLIB_release를 호출하는 걸 잊어 버리기 쉬움 - 러스트 개발자는 RAII로 래퍼 객체를 만들고 싶어 하지만, C API를 사용하는 테스트에서는 이 기능을 쓸 수 없음
- 복잡한 로직에서는 정리 함수를 항상 호출하기 어려움. C에서는
goto를 써서 해결하지만 러스트는 지원하지 않음 defer크레이트로 해결했으나 빌림 체커가 좋아하지 않음
크로스 컴파일이 항상 작동하지는 않음
- Miri와 마찬가지로 C나 어셈블리로 구현된 부분이 있는 라이브러리를 사용하면
cargo build --target=...이 바로 작동하지 않음
Cbindgen이 항상 작동하지는 않음
- Cbindgen은 러스트 코드베이스에서 C 헤더를 생성하는 데 많이 쓰이지만 한계나 버그가 있음
불안정한 ABI
Option같은 유용한 표준 라이브러리 타입에 안정된 ABI가 없어서repr(C)어노테이션으로 수동 복제해야 함
커스텀 메모리 할당자 지원 부재
- 많은 C 라이브러리는 사용자가 런타임에 할당자를 제공할 수 있음. 러스트에서는 컴파일 타임에만 전역 할당자를 고를 수 있음
- 자원 정리 이슈는 아레나 할당자로 해결할 수 있지만, 러스트에서는 관용적이지 않고 표준 라이브러리와 통합되지 않음
복잡성
UnsafeCell이나RefCell,MaybeUninit,Pin같은 걸 FFI 처리를 위해 써야 해서 복잡성이 높음- 순수 러스트도 이미 복잡한데 FFI 레이어까지 더해지면 야수가 됨
- 러스트 복잡성 때문에 이 코드베이스 작업을 거절한 개발자들도 있었음
결론
- 러스트 재작성에 대체로 만족하지만 일부 영역에서는 실망스러웠고, 예상보다 훨씬 더 많은 노력이 들었음
- C와 많이 상호작용하는 러스트는 순수 러스트를 사용하는 것과는 완전히 다른 언어처럼 느껴짐. 마찰이 많고 함정도 많음. 러스트가 해결했다고 주장하는 C++의 많은 이슈가 사실 전혀 해결되지 않음
- 러스트, Miri, cbindgen 등의 개발자들에게 깊이 감사함. 그들은 엄청난 일을 해냈음. 그럼에도 불구하고 C FFI를 많이 할 때의 언어와 도구는 미성숙하고 거의 v1.0 이전 같이 느껴짐
unsafe의 ergonomics, 표준 라이브러리, 문서, 도구, 불안정한 ABI 등이 향후 개선된다면 더 즐거운 경험이 될 수 있을 것임- 마이크로소프트와 구글도 이 모든 점을 느꼈기에 이 영역에 실제 자금을 투자하고 있는 것으로 보임
- 아직 Rust를 모른다면 첫 프로젝트는 순수 Rust를 사용하고 FFI 주제와는 거리를 두는 것이 좋음
- 처음에는 이 재작성에 Zig이나 Odin을 사용하는 것을 고려했지만 기업 프로덕션 코드베이스에 v1.0 이전 언어를 사용하고 싶지 않았음. 이제는 경험이 Rust보다 정말 나빴을지 궁금해짐. 아마도 Rust 모델이 C 모델(또는 C++ 모델)과 정말 맞지 않아서 둘을 함께 사용할 때 마찰이 너무 심한 것 같음
- 앞으로 비슷한 작업을 해야 한다면 Zig을 강력히 고려할 것임. 누군가 "그냥 Rust로 다시 작성하세요"라고 말할 때마다 이 글을 보여주고 마음이 바뀌었는지 물어보시길