Canonical이 Ubuntu의 coreutils를 GNU에서 Rust 구현으로 전환하려는 결정과 관련해, 보안과 성능 측면의 이점과 한계를 짚고, 재작성 여부가 아니라 기존 두 구현 중 어느 것을 사용할지라는 관점에서 논의한다. 또한 이 전환 과정이 배포판 핵심에 Rust를 도입하는 장애물을 식별·해소하는 촉매가 될 수 있음을 pyca/cryptography 사례와 함께 설명한다.
토, 2025년 3월 22일
Canonical이 최근 발표했듯이, Ubuntu가 제공하는 coreutils를 오랜 역사의 업스트림 GNU coreutils에서 더 최신의 Rust 구현으로 전환하는 길을 걷고 있다. 이 소식은 어떤 사람들을 크게 들뜨게 했고, 또 어떤 사람들은 매우 불쾌하게 만들었다(중간은 거의 없는 듯하지만, 이는 표본 편향 탓일지도 모른다). 이 글에서는 이 변화에 대한 장단점을 살펴본다.
가장 먼저 시작할 지점은, Rust로 작성된 프로그램에 찬성하는 가장 강력한 일반적 논거인 메모리 안전성이다. C와 C++로 작성된 프로그램과 비교하면, Rust 프로그램은 기본적으로 메모리 안전하므로 취약점이 크게 줄어드는 경향이 있다. 이는 널리 좋은 것으로 받아들여진다.
하지만 C로 작성된 많은 대규모 프로그램과 달리, coreutils는 메모리 비안전성으로 인한 보안 취약점의 유의미한 이력을 특별히 가지고 있지 않다. 약간의 일차 원리적 추측을 섞자면, 이는 개별 coreutils 바이너리 대부분이 상대적으로 작고 공격 면이 꽤 제한적이기 때문일 가능성이 가장 크다. 더 나아가, 네트워크 프로토콜 파싱 라이브러리 같은 것과 달리, 대부분의 coreutils 바이너리는 보안상 민감한 맥락에서 사용되지 않는다. 이런 이유로 보안 측면에서 coreutils는 Rust의 이점을 강하게 주장할 사례가 되지 못한다.
Rust에 찬성하는 다음 논거는 대개 성능이다. 이 논거의 가장 강한 버전은 Rust를 Python이나 Ruby, 혹은 Java나 Go와 비교할 때이다. 이 경우 Rust는 메모리 할당에 대한 정밀한 제어, GC(가비지 컬렉터) 부재, 기타 오버헤드를 피하는 기능의 이점을 누리면서도 높은 수준의 추상화를 제공할 수 있다(예: 100개의 HTTP 요청을 보내는 프로그램을 작성하는 경험은 Rust와 Python이 꽤 비슷하게 보인다). 그러나 C와 비교하면 Rust의 성능에 유리한 논거는 더 흐릿해진다. 어떤 이들은 Rust의 엄격한 별칭(strict aliasing) 규칙이 더 많은 컴파일러 최적화를 이끈다고 지적하지만, 이것이 거시적으로 유의미한 이점을 제공하는지는 불분명하다(작은 커널에서는 큰 이득을 보이기 쉽지만, 이것이 대형 프로그램 전반에서 측정 가능한 성능 차이로 이어지는 경우를 나는 본 적이 없다). 약간 다른 논거로는, C와 Rust 모두로 동등한 성능의 프로그램을 작성하는 것이 완전히 가능하더라도, C에서 그렇게 하는 것이 번거로울 수 있다는 점이 있다(특히 작은 코드베이스일수록). 예를 들어, 고성능 해시 테이블이 도움이 되는 알고리즘이 있을 때 Rust에는 이미 쓸 만한 것이 기본 제공되지만 C에는 없다. 직접 작성하거나 라이브러리에 의존할 수 있지만, C에는 제네릭이나 표준 패키지 관리자가 없기 때문에 불편함을 겪게 된다. 원리적으로 보면 C의 해시 테이블도 Rust의 해시 테이블만큼 빠를 수 있지만, 엔지니어 입장에서는 이미 준비된 것을 그냥 가져다 쓸 수 있다는 점이 상당한 장점이다. 그렇다고 해도, Rust coreutils 프로그램이 실제로 GNU coreutils보다 더 빠른지(아니면 어느 것은 더 빠르고 어느 것은 더 느린지, 그리고 그 차이가 어느 정도인지)는 여전히 불분명하다.
지금까지의 점수표로 보면, coreutils를 갈아엎자는 주장의 힘은 비교적 약하다. 내가 ‘인터넷의 엔지니어링 총괄 부사장(SVP of Engineering for The Internet)’이라면 아마 이 프로젝트에 인력을 배치하지 않을 것이다. 하지만 나는 그런 직책이 아니다. 사실 그런 사람은 아무도 없다. 어떤 이들은 그들만의 이유로 coreutils의 Rust 구현을 만들었다. 오픈 소스 소프트웨어의 엄청난 특징 중 하나는, 사람들이 그냥 무언가를 만들 수 있고 굳이 정당화를 하지 않아도 된다는 점이다. 따라서 우리가 맞닥뜨린 질문은 “누군가 coreutils를 다시 써야 하는가?”가 아니라, “이제 coreutils의 두 가지 구현이 생겼으니, 우리는 어느 것을 써야 하는가?”이다. 그리고 그것은 완전히 다른 질문이다. 지금까지 논의한 바를 토대로 하면, Rust coreutils 구현이 약간의 주변적 이점을 가질 수는 있겠지만 큰 이점은 아닐 수도 있다. 하지만 우리가 물어야 할 질문은 ‘재작성해야 하는가’가 아니라 ‘누군가 이미 재작성했으니, 그것을 사용할 것인가?’이다.
마지막으로, Ubuntu가 coreutils의 Rust 구현으로 전환할 수 있도록 준비하는 작업 자체가 가져올 또 다른 이점을 생각해보고자 한다. 그 과정에서 배포판의 핵심 부분에 Rust를 사용하기 어렵게 만드는 수많은 장애물을 필연적으로 드러내고 해결해야 할 것이며, 그 작업이 Rust의 메모리 안전성이 실제로 이득이 되는 배포판의 다른 구성 요소들에 Rust를 사용하는 길을 닦아줄 것이다.
이것은 우리가 pyca/cryptography에서 겪었던 경험이기도 하다. Rust 코드를 포함한 초기 릴리스는 Rust의 이점을 전혀 활용하지 않았는데(빌드 인프라를 시험하기 위한 no-op 모듈이었다), 그럼에도 이를 배포하는 과정에서 기술적·사회적 과제들이 대거 표면화되었다. 예컨대 Rust 설치 방법에 대한 더 명확한 오류 메시지와 안내부터, libc로 musl을 사용하는 시스템을 위한 Python 바이너리 배포 표준의 필요성까지 말이다. 그 작업을 해낸 뒤 우리는 a) 보안과 성능 양면에서 큰 이점을 얻는 라이브러리 부분에 Rust를 훨씬 더 많이 사용할 수 있었고(예: OpenSSL의 X.509 파싱을 우리 구현으로 대체하여 10배 속도 향상을 달성), b) 다른 Python 프로젝트들이 Rust를 채택하기 훨씬 쉽게 만들었다. 그래서 초기 릴리스가 Rust의 이점을 보여주지 못했음에도, 이는 향후 큰 성과를 위한 무대를 마련했다.
Canonical이 Rust coreutils를 채택하는 일이 같은 역할을 해낼 수 있다면, 즉 배포판의 핵심에서 Rust를 배포하기 어렵게 만드는 장애물을 식별하고 해결하는 데 기여할 수 있다면, 그것은 대단히 가치 있을 것이며 Rust가 진가를 발휘하는 영역에서의 사용을 가능하게 해줄 것이다.