Rust에서 Ruby로

2 days ago 4
  • Rust 웹앱 크레이트를 Ruby on Rails로 옮겨보는 개인 프로젝트 실험이며, 대상은 Tera와 Axum 기반 14,943줄 코드임
  • 기존 Rust 구성은 Playwright E2E, 격리 DB 네임스페이스, 모킹 서비스, 내부 API 크레이트까지 필요해 테스트 비용이 큼
  • LLM 비교에서 Rails는 총점 710으로 Rust/Axum/Diesel의 480보다 높고, 개발 속도와 단위 테스트 용이성이 강점으로 평가됨
  • Local Qwen3.6 원샷 변환은 약 30분 걸렸고 Ruby 코드는 3,322줄로 줄었지만, 아직 실행 검증은 안 됨
  • Rails는 기본 기능과 간결한 테스트가 장점이며, Ruby의 타입 안정성 부족은 Sorbet이나 에이전트 기반 타입 추가로 보완 가능함

전환 실험의 배경

  • 개인 프로젝트의 일부인 Rust 웹앱 크레이트가 Ruby on Rails 전환 대상으로 선택됨
    • 전체 프로젝트는 약 3만 줄 규모이고, 전환 대상 크레이트는 TeraAxum으로 작성된 웹앱에 가까움
    • 전환 대상 Rust 코드는 총 14,943줄이며, 컴파일에는 약 10초가 걸림
    • 코드 자체는 크지 않지만 의존성이 많이 따라붙는 구조임
  • 기존 Rust 구성은 테스트 비용이 높음
    • E2E 테스트에는 Playwright 설정이 필요함
    • 모킹이 어려워 격리된 데이터베이스 네임스페이스와 모킹 서비스가 필요함
    • Playwright가 헤드리스 모드에서 앱과 상호작용하도록 별도 내부 API 크레이트도 필요함
  • Ruby와 Ruby on Rails는 간결한 대안으로 검토됨
    • Ruby는 타입이 없어 안정성이 Rust보다 떨어질 수 있음
    • Sorbet을 쓰면 Ruby에서도 타입 안정성을 일부 보완할 수 있음
  • 여러 LLM 인스턴스로 복잡도, 안정성, 테스트 용이성 등을 비교한 결과 Rails 점수가 더 높게 나옴
    • Rust/Axum/Diesel 합계는 480, Rails는 710, Rails + Sorbet은 695로 계산됨
    • Rails는 개인 개발자 적합성 90, 개발 속도 90, 단위 테스트 용이성 90으로 높게 평가됨
    • Rust/Axum/Diesel은 안전성 95, 성능 95로 높지만 단위 테스트 용이성 20, 통합 테스트 용이성 30으로 낮게 평가됨
    • 단순 합계 기준으로 Rails 앱이 1.47배 더 나은 결과를 낼 수 있다고 판단됨

변환 결과와 검토 포인트

  • Local Qwen3.6으로 비교적 작은 프로젝트를 원샷 변환함
    • 변환에는 약 30분이 걸림
    • 아직 실행해보지 않아 실제 작동 여부는 확인되지 않음
  • 가장 큰 변화는 코드 줄 수 감소임
    • Rust 파일 총 줄 수: 14,943줄
    • Ruby 파일 총 줄 수: 3,322줄
    • 줄 수가 77% 감소했고, Ruby 1줄당 Rust 약 4.49줄에 해당함
  • 변환된 Ruby 코드는 훑어본 범위에서 깨끗하고 관용적으로 보임
    • 버그 가능성은 남아 있음
    • 이후 더 면밀히 검토할 예정임
  • 추가 검토 포인트는 타입 보완, Rails의 기본 기능, 테스트 단순화임
    • 에이전트를 이용해 타입을 추가하면 타입 안정성 문제를 완화할 수 있음
    • Ruby/Rails는 “batteries + kitchen sink included”에 가깝고, 3GiB 규모의 컴파일된 의존성보다 낫다고 평가됨
    • 테스트는 훨씬 쉬워질 것으로 기대됨
  • Ruby 테스트 예시는 VCR.use_cassette("llm_call")로 LLM 호출을 감싸고 결과 크기를 검증하는 짧은 형태임 VCR.use_cassette("llm_call") do result = LlmClient.match(entry, data_list) expect(result.results.size).to eq(data_list.size) end
  • Rust 테스트 예시는 모킹 제공자를 직접 구현해야 하는 더 긴 형태임
    • Arc<RwLock<Vec<Response>>>, AtomicUsize, async_trait, tokio::test 등을 사용함
    • 응답 목록과 호출 횟수를 관리하는 MockProvider를 만들고, Provider 트레이트의 match를 구현한 뒤 테스트에서 결과와 호출 횟수를 검증함
  • 개인 프로젝트이기 때문에 과감한 선택이 가능하며, Rust에서 Ruby로의 전환은 앞으로 면밀히 검토될 예정임
Read Entire Article