Ruby에서 네이티브 확장을 구축하는 주된 동기는 성능 향상이지만, 강연자는 순수 Ruby 코드가 특정 상황에서 네이티브 확장보다 빠를 수 있음을 벤치마크를 통해 보여주며 무조건적인 네이티브 확장 도입을 경계합니다. 대신, 실제 성능 병목 지점을 해결하기 위한 대안 중 하나로 Rust 확장을 고려할 것을 제안합니다. Ruby용 Rust 확장을 생성하는 과정은 bundle gem --ext rust
명령어를 통해 시작되며, Cargo.toml
, extconf.rb
, lib.rs
와 같은 핵심 파일들이 이 과정에서 생성됩니다. 특히 extconf.rb
는 사용자의 시스템 환경에 맞춰 확장을 컴파일하는 레시피 역할을 하며, lib.rs
는 실제 Rust 소스 코드를 담고 있습니다. Ruby VM은 Init_모듈명
이라는 명명 규칙을 따르는 함수를 찾아 동적으로 링크된 공유 객체(Shared Object, .bundle
, .so
, .dll
파일)를 로드하고 실행합니다. 이 과정에서 magnus
와 rb_sys
라이브러리가 중요한 역할을 합니다. magnus
는 고수준 바인딩을 제공하여 Rust 데이터 타입을 Ruby 데이터 타입으로 변환하는 작업을 안전하게 처리하며, rb_sys
는 더 낮은 수준의 C 바인딩을 제공하지만 일반적으로 개발자가 직접 다룰 필요는 없습니다.
생산 환경에 네이티브 확장을 배포하기 전에는 몇 가지 중요한 고려 사항이 있습니다. 첫째, 오류 처리입니다. Rust의 unwrap
과 같은 패닉을 유발하는 코드는 Ruby 프로그램의 실행을 중단시킬 수 있으므로, Result
타입을 사용하여 오류를 명시적으로 처리하고 Ruby 예외로 변환하여 던져야 합니다. 둘째, 메모리 누수 관리입니다. Rust는 메모리 안전성을 표방하지만, unsafe
블록 사용이나 의존성 라이브러리로 인해 메모리 누수가 발생할 수 있습니다. valgrind
나 ruby-memcheck
와 같은 도구를 사용하여 메모리 누수를 정기적으로 검사하고 관리해야 합니다. 셋째, 배포의 편리성입니다. 사용자가 확장을 설치할 때마다 소스 코드로부터 컴파일하는 것은 시간이 오래 걸리므로, 가장 많이 사용되는 플랫폼에 대해 미리 컴파일된 번들(pre-compiled bundles)을 제공하는 것이 좋습니다. GitHub Actions와 같은 CI/CD 파이프라인을 활용하여 릴리스 시 자동으로 여러 플랫폼용 번들을 생성하고 배포할 수 있습니다.
강연자는 fast_toml
이라는 TOML 파서 젬을 예시로 들어, Rust 기반 네이티브 확장이 기존 Ruby 기반 파서보다 훨씬 빠른 성능을 달성할 수 있음을 벤치마크를 통해 입증했습니다. 다만, 파싱 성능은 뛰어나지만 TOML 데이터를 다시 문자열로 생성하는 과정에서는 기존 Ruby 옵션보다 빠르지 않다는 한계점도 명확히 밝혔습니다. 이는 특정 작업에 대한 최적화를 의미하며, 모든 기능이 네이티브 확장으로 대체될 필요는 없음을 시사합니다.