소프트웨어 개발자에게 “안전”이란 어떤 의미일까? 메모리 누수 없는 코드? 스레드 안전성? 아니면 예측 가능한 동작? 러스트는 이런 질문들에 대한 해답처럼 등장했다. 컴파일 타임에 메모리 안전성을 보장한다는 약속은 개발자들을 매료시켰고, “Rewrite It In Rust”는 거의 종교적 신념처럼 번졌다. 그런데 Bun의 러스트 포팅에서 발견된 13,365개의 unsafe 블록은 이 신화에 금을 내고 있다. 숫자만으로도 압도적인 이 통계는 단순한 통계 이상이다. 러스트의 안전성에 대한 근본적인 질문을 던진다: 우리는 정말 안전하다고 믿는 언어에서 얼마나 많은 ‘불안전’을 용인할 준비가 되어 있는가?
Bun의 사례는 러스트의 unsafe가 단순히 예외적인 상황이 아님을 보여준다. 전체 unsafe 블록 중 약 69%가 래퍼, 재설계, 검사를 통해 안전하게 전환될 수 있다는 분석은 두 가지를 시사한다. 첫째, unsafe 사용은 종종 편의성이나 성능을 위해 의도적으로 선택된 결과라는 점. 둘째, 러스트의 안전성 보장은 설계 단계에서부터 적극적인 노력을 요구한다는 점이다. 이는 언어 자체의 한계라기보다, 시스템 프로그래밍의 본질적 복잡성을 반영한다. 메모리 관리, 저수준 최적화, 하드웨어와의 직접적인 상호작용은 때로 러스트의 안전성 검사를 우회해야 하는 상황을 만든다.
문제는 unsafe 블록의 존재 자체가 아니다. 그 블록들이 어떻게 관리되고 있는지다. Bun 코드베이스에서 발견된 일부 사례는 러스트의 안전성 철학을 정면으로 위반한다. 예를 들어 수명 주기를 무시하고 ‘static으로 캐스팅하는 패턴은 메모리 안전성의 근간을 흔든다. 이는 unsafe의 남용이 아니라, 러스트의 안전성 모델에 대한 무지에서 비롯된 결과일 수 있다. 더 우려스러운 점은 이런 코드가 “기본적인 miri 검사조차 통과하지 못한다”는 사실이다. miri는 러스트의 안전성 검사를 위한 도구로, 이를 통과하지 못하는 코드는 심각한 메모리 안전성 문제를 내포하고 있음을 의미한다.
unsafe는 러스트의 안전성 보장을 일시적으로 해제하는 스위치다. 하지만 그 스위치를 켜는 순간, 개발자는 컴파일러 대신 스스로 안전성을 보장해야 하는 책임을 진다.
Bun의 사례는 러스트가 안전성을 보장한다고 해서 모든 문제가 해결되는 것은 아님을 여실히 보여준다. 오히려 러스트는 안전성에 대한 책임을 개발자에게 더 명확히 할 뿐이다. unsafe 블록의 존재는 시스템 프로그래밍의 현실을 반영한다. 때로는 성능, 때로는 하드웨어와의 직접적인 상호작용을 위해 안전성 검사를 우회해야 한다. 하지만 이런 우회가 일상화될 때, 러스트의 안전성 보장은 무의미해진다. 중요한 것은 unsafe 블록의 수나 비율이 아니라, 그 블록들이 얼마나 신중하게 관리되고 있는지다.
이 논의의 핵심은 러스트가 안전하지 않다는 주장이 아니다. 오히려 러스트는 안전성에 대한 책임을 개발자에게 명확히 할 뿐이라는 점이다. Bun의 사례는 러스트의 안전성 모델이 얼마나 강력한지, 그리고 동시에 얼마나 취약한지를 동시에 보여준다. unsafe 블록은 러스트의 안전성 보장을 일시적으로 해제하는 스위치다. 하지만 그 스위치를 켜는 순간, 개발자는 컴파일러 대신 스스로 안전성을 보장해야 하는 책임을 진다. 이는 시스템 프로그래밍의 본질적 딜레마다. 저수준 제어와 안전성 사이에는 항상 트레이드오프가 존재한다.
Bun의 러스트 포팅이 주는 교훈은 명확하다. 러스트는 안전성을 보장하는 마법의 도구가 아니다. 안전성은 언어의 기능이 아니라, 개발자의 설계와 실천의 결과다. unsafe 블록의 존재는 러스트의 한계가 아니라, 시스템 프로그래밍의 현실을 반영한다. 중요한 것은 이런 블록들이 어떻게 관리되고 있는지, 그리고 안전성 보장을 위한 노력이 얼마나 지속적으로 이루어지는지다. Bun의 사례는 러스트의 안전성 신화를 깨는 것이 아니라, 그 신화가 얼마나 섬세한 균형 위에 서 있는지를 보여준다.
더 자세한 내용은 Bun의 unsafe 블록 감사 보고서에서 확인할 수 있다.
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.