GN⁺: Rust로 게임 개발을 한 3년 후에 떠나며
(loglog.games)- Rust에 익숙해지면 모든 문제가 사라질 것이라는 주장에 대해
- Rust에 익숙해져도 근본적인 문제는 사라지지 않음
- 게임은 복잡한 상태 머신이고 요구사항이 계속 바뀌기 때문에 Rust의 정적이고 과도하게 검사하는 특성과 맞지 않음
- 코드를 계속 리팩토링해야 하는 문제는 self-inflicted임
- Borrow checker로 인해 발생하는 대규모 리팩토링 문제
- Rust는 다른 언어보다 더 자주 리팩토링을 강제함
- 게임 요구사항이 자주 바뀌는 상황에서 borrow checker와 싸우게 되고 코드 재구조화가 필요해짐
- 리팩토링이 좋은 코드를 만든다는 주장에 대해선 동의하기 어려움. 좋은 코드는 아이디어를 반복하고 시도해보면서 만들어지는 것
- 간접화(Indirection)는 일부 문제만 해결하고 개발 생산성을 떨어뜨림
- Borrow checker 이슈를 해결하기 위해 간접화를 사용하면 코드 로직이 분리되고 복잡해짐
- 게임에선 연결된 이벤트, 특정 타이밍, 많은 상태 관리가 필요한데 간접화는 이를 어렵게 함
- Rust에선 간단한 코드도 간접화를 위해 장황하게 써야함
- ECS(Entity-Component-System)는 잘못된 문제를 해결함
- ECS의 장점은 사실 generational arena의 장점임
- ECS를 dynamic composition, structure of arrays, Rust borrow checker 해결책, dynamically created generational arenas 등 다양한 관점에서 봄
- 하지만 게임 개발에서 ECS가 가장 적합한 방식인지는 의문. 퍼포먼스나 컴포지션보다는 게임 로직에 더 신경써야 함
- 일반화된 시스템(Generalized systems)은 재미있는 게임플레이로 이어지지 않음
- 너무 일반화된 시스템을 사용하면 지루한 게임플레이가 만들어짐
- 좋은 게임은 세부적인 상호작용을 주의깊게 설계하고, VFX를 동기화하며, 플레이테스트와 실험을 반복하고, 빨리 출시해서 피드백 받는 것이 중요함
- Rust는 빠른 프로토타이핑과 반복(iteration)에 적합하지 않은 가치를 추구함
- 게임 개발에서 중요한 것은 신속한 프로토타이핑과 반복이지만 Rust의 가치는 그 반대임
- 게임 개발의 핵심은 게임 자체와 플레이 경험이지 크래시 없는 코드가 아님
- 게임 코드의 유지보수성은 인디 게임에선 그다지 중요한 가치가 아님. 중요한 건 반복 속도(iteration speed)
- Rust는 문제를 피하는데 집착한 나머지 정작 중요한 것을 놓치고 있음
- Procedural macros는 리플렉션에 훨씬 못 미침
- 게임 개발에선 시스템 코드, 게임플레이 코드, UI, VFX, 오디오, 툴 등 다양한 영역의 코드를 작성해야 함
- Rust에선 간단한 "객체 출력"도 코드를 직접 짜거나 procedural macros를 만들어야 함
- Procedural macros는 선언적 매크로에 비해 훨씬 제한적이고 컴파일 시간도 오래 걸림
- C#의 리플렉션은 매우 사용하기 쉽고 퍼포먼스가 중요하지 않은 경우 빠르게 개발할 수 있게 해줌
- Hot reloading이 반복 속도 향상에 중요한 역할을 함
- Hot reloading은 즉시 모드 UI/그리기, 디버깅, 게임플레이 상수 튜닝 등에 매우 유용함
- Rust에도
hot-lib-reloader
가 있지만 완벽하진 않고 계획과 선견지명이 필요해서 창의적 사용이 제한됨 - 게임 개발자는 단순한 struct 리로딩 이상의 고차원적인 툴링을 원함
- 추상화는 선택이 아니라 필수
- 실제 예시) UI 상태에 따라 다른 동작을 해야하는 경우 Rust에선 리팩토링이 필요하거나 클론을 남발하게 됨
- 비즈니스 로직이 바뀌는 게 아니라 컴파일러를 만족시키기 위해 코드를 바꾸는 경우가 많음
- Rust는 "이 필드들을 가진 타입"같은 구조적 타입 시스템이 없어서 리팩토링시 코드 여러군데를 수정해야함
- Rust의 GUI 상황은 최악임
- 게임 UI에서 중요한 건 데이터 바인딩이나 반응형 업데이트가 아니라 외관을 커스텀하는 것
- 게임 UI에 필요한 건 이쁜 GUI, 커스텀 스프라이트, 애니메이션, 벡터 쉐입, 파티클, 효과, 플래쉬 등
- 현재 게임 GUI에 특화된 Rust 라이브러리는 없음
- Orphan rule은 옵셔널이어야 함
- Orphan rule은 안전을 위해 개발 생산성을 크게 떨어뜨림
- 라이브러리가 아닌 어플리케이션 코드에선 orphan rule을 끌 수 있어야함
- 컴파일 시간은 개선되었지만 proc macros를 쓰면 여전히 느림
-
serde
와 같은 proc macros는 컴파일 시간을 크게 증가시킴 - 20-30초 이상 걸리는 incremental build에선 세부 튜닝이 매우 어려움
-
- Rust 게임 개발 생태계는 과대 포장된 느낌
- 실제 게임을 만들고 있는 사람은 많지 않음
- 웹사이트나 README가 화려하고 유명해 보이는 프로젝트가 실제로는 그렇지 않은 경우가 많음
- 실제 게임을 만들고 싶다면
godot-rust
를 추천. 퓨어 Rust 솔루션에 집착하지 말고 성숙한 엔진의 도움을 받는 게 좋음
- 게임은 싱글 스레드라서 전역 상태가 불편한 이유가 잘못됨
- Rust에서 전역 상태를 사용하는 건 매우 불편함 (
static mut
,AtomicRefCell
,Rc
등) - 하지만 게임은 백엔드 서비스와 달리 모든게 싱글 스레드라서 이런 불편함이 필요하지 않음
- Bevy가 병렬 시스템을 도입한 건 실수라고 봄. 빠른 퍼포먼스를 위해 했지만 사용자가 순서를 계속 명시해야 하는 등 오히려 불편함만 가중됨
- Rust에서 전역 상태를 사용하는 건 매우 불편함 (
- 동적 borrow 체킹은 리팩토링 후에 예상치 못한 크래시를 일으킴
-
hecs
를 2년간 사용하면서 query가 겹치면서 크래시가 발생하는 사례를 자주 봄 -
RefCell
을 쓸 때도 두 곳에서.borrow_mut()
하면 예상치 못한 크래시 발생 - 코드가 나쁘다기보다는 Rust가 불필요한 리팩토링을 강제하는 것
- 게임에선
RefCell
이 유용한데 Rust는 이를 너무 어렵게 만듦
-
- Context 객체의 유연성이 부족함
- Rust에선 전역 상태를 쓰기 어려워서 context 객체에 레퍼런스를 모아 전달하는 패턴을 씀
- 하지만 이 때 일부 필드만 빌려쓰면 partial borrow 때문에 컴파일 에러가 발생함
- Context를 쪼개서 구조를 바꾸라고 하지만 정작 하고픈 건 게임 로직인데 컴파일러 만족을 위해 구조를 바꾸는 건 시간 낭비임
Rust의 장점
- 컴파일 되면 보통은 잘 동작함. 특히 CLI 도구나 데이터 처리, 알고리즘 작성시 유용함
- 별다른 노력없이 좋은 퍼포먼스를 보여줌. C#보다 1.5~2.5배 정도 빠른 경험이 있음
- Enum이 잘 구현되어 있음
- Rust analyzer 덕분에 IDE 사용성이 크게 개선됨
- Trait 시스템이 잘 되어 있음. Orphan rule만 좀 완화된다면 훨씬 활용도가 높아질 것
GN⁺의 의견
-
Rust에 대한 의견이 대체로 부정적이긴 하지만, 저자가 다양한 시도를 해보고 내린 결론인 만큼 진지하게 받아들일 필요가 있어 보임. 특히 게임 개발이란 도메인에서 실용성과 개발 속도가 얼마나 중요한지, 그리고 Rust가 그런 부분에선 아직 부족한 면이 있다는 지적은 일리가 있음.
-
Rust가 지향하는 안전성이 때로는 개발 생산성을 심각하게 저하시킬 수 있다는 것에 동의. Rust 코드를 짜다보면 로직 자체보다는 컴파일러를 만족시키는데 더 많은 시간을 쓰게 되는 경우가 많고, 이는 Rust가 근본적으로 시스템 프로그래밍에 특화된 언어라는 점에서 기인하는 것 같음
-
반면 게임 개발은 대부분 싱글 스레드 환경이고 빠른 프로토타이핑과 반복이 중요한 영역이라, 지나친 안전성 검사는 오히려 독이 될 수 있을 것. 특히 Rust 초창기에 게임 개발을 시도했다가 좌절한 경험들을 많이 들어봤는데, 아직도 근본적인 문제점들은 크게 개선되지 않은 듯.
-
물론 Bevy 등 최근에 나온 게임 엔진들이 Rust 게임 개발의 편의성을 높여주긴 했지만, 아직 Unity나 Godot같은 성숙한 엔진들의 수준에는 한참 못 미치는 것 같음. 이 글에서처럼 Rust로 퓨어하게 모든 걸 만들기보다는 기존 엔진들과 함께 사용하는 게 현실적인 대안이 될 수 있음
-
저자가 언급한 장점들은 게임이 아닌 다른 분야에서 Rust를 사용할 때 크게 와닿음. 특히 시스템 프로그래밍이나 웹 서버 개발 같은 영역에서 Rust의 안전성과 높은 퍼포먼스는 큰 무기가 되지만 그런 장점들도 게임 개발에선 그다지 큰 메리트로 작용하지 않는 듯
-
결국 Rust는 만능 언어가 아니라 특정 도메인에 특화된 언어인 만큼, 자신의 프로젝트 성격에 맞는지 신중히 고려해봐야 할 것 같음
rust 는 코어 레벨에서 무결성 있는 코드를 짤 수 있는 개발자들이 적어지고,
다양한 개발자 폴트에 의해서 발생하는 문제들을 해결하기 위한 언어라고 생각합니다.
빠르게 개발하는게 아니라,
정확하게 개발하는데 더 적합하기 때문에,
사용자 추가 요구사항이 빈번하게 발생하고, 실행해야하는 프로젝트에는 적합하지 않습니다.
그럼에도 불구하고, UI 라이브러리가 나오길 소망하는 것은
견고한 코드 위에 동작하는 작고 심플한 UI만 있어도 더 활용성이 좋아질 것이라는 기대감 때문일 것 같아요.
게임 개발을 해본적이 없어서 잘 모르겠지만,
처음부터 언어를 잘못 골랐거나, (개발 iteration이 중요했으면 script수준언어를 택했어야..)
시스템에대한 깊은 이해가 없거나 한거같아요.
c++을 선택했어도 비슷한 문제를 격지 않았을까 하네요.
Hacker News 의견
메타버스 클라이언트 개발 경험을 공유하며 러스트의 문제점들을 제시함
- 러스트로 대규모 3D 게임을 개발하는 사례가 거의 없음
- 리팩토링과 프로그램 각 부분을 연결하는 작업이 고통스러움
- 렌더링은 거의 적절하지만 스택이 완성되지 않았고 신뢰할 수 없음
- 2D GUI 시스템은 약하고 다이얼로그 박스당 너무 많은 코드가 필요함
- async 시스템이 불필요한 영역까지 확산되는 문제에 동의함
20년 경력의 게임 개발자로서 러스트가 게임 개발에 적합하지 않다고 봄
- 게임플레이 코드는 유연해야 하고 흥미로운 게임을 만들기 위해 엣지 케이스가 많아야 함
- 컴파일 시간이 중요하고 구조 변경이 자주 필요함
- ECS는 일부 시스템에 유용하지만 게임플레이나 UI 코드에는 사용하기 힘듦
러스트 게임 개발 생태계는 과대광고에 의존하고 있음
- 러스트로 게임 개발을 시도했지만 즉시 끔찍한 경험을 함
- 느린 컴파일 시간, 거대한 패키지 다운로드, 러스트 자체의 어려움 등
- 다른 도구들이 러스트처럼 안전하지 않다고 주장하지만 게임에서 메모리 안전성이 큰 문제였던 적이 거의 없음
비비와 유니티, 고도로 게임 개발 경험을 비교함
- 현재 러스트로 게임 개발을 선택하는 것이 이해하기 어려움
- ECS에 모든 것을 우겨넣으면 재미있는 게임플레이 개발에 방해가 됨
- 현대 PC의 성능을 고려하면 다중 스레딩 최적화를 과도하게 걱정할 필요가 없음
님(Nim) 언어가 러스트보다 게임 개발에 더 적합할 수 있음
- 개발자를 방해하지 않고 핫 리로딩을 지원함
러스트는 특정한 방식의 프로그래밍을 강요하는 독선적인 언어로 보임
- 메모리 안전성을 모든 것보다 우선시 하는데 게임 개발에서는 그다지 유용하지 않음
- 게임 개발에 이상적인 언어는 생산성이 높고 성능이 적절하며 위험을 감수할 줄 아는 숙련된 프로그래머를 위한 것이어야 함
Allen Blomquist의 툴링 데모는 개발 피드백 루프가 중요함을 보여줌
- 러스트가 원하는 작업 환경을 제공하지 못해 Unity로 다시 전환함
러스트의 근본적 문제는 객체지향을 버리고 함수형 프로그래밍을 선호하는 것
- 최하위 코어에서는 도움이 될 수 있지만 상위 레벨에서는 적합하지 않음
게임 개발에 있어 러스트는 최악의 선택
- 정확성과 안전성, 완벽한 코드를 추구하는 러스트의 가치는 게임 개발과 맞지 않음
- 게임은 수명이 짧고 개발 주기도 짧아 코드 품질이 그리 중요하지 않음
- 게임 엔진 개발에는 도움이 될 수 있지만 높은 유연성의 스크립팅 언어와 함께 사용해야 함
D언어로 코딩하면서 C에 비해 데이터 레이아웃 변경이 훨씬 쉬움을 깨달음
- 참조 타입과 값 타입 간 전환이 C에서는 어렵지만 D에서는 쉬움
요즘 시대에 Rust, C++ 같은 네이티브 언어들은 게임 클라이언트 개발 보다는 게임 엔진 같은 미들웨어 개발이 더 적절해보인다는 생각이 드네요.
네이티브 언어로 풀스택 웹 개발하는 사례가 거의 없듯이요.
그렇죠. 원래 파폭 내부 엔진 개선용으로 시작되었던가요?
글 내용은 만병통치약 같은 느낌으로 러스트와 잘 안 맞는 분야에서 개발을 해봤다가 고생한 그런 느낌이네요 ㅋㅋ
Rust를 시작하는 단계라 조금 공감이 가네요.
Borrowing 변경으로 코드가 너무 많이 수정되어서 고통스럽더라구요.
암시적 표현이 많은 python, javascript과 달리 rust는 명시적인 경향이 있어서 장황한 코드가 만들어지기도 하고, 프로그래머에게 많은 선택을 하게 만드니 피곤하기도 하고요.
그럼에도 다른 언어에 없는 개념들이 신선하고 재밌어요.
Rust Analyzer나 GitHub Copilot이 없었더라면 일찍 포기했을 거 같아요.
러스트로 코어부터 프론트까지 짜는 것 보다는
유니티나 다른 게임 엔진 처럼 코어는 러스트로 작성하고 생산성 높은 언어를 결합해서 쓰게 되면 어떨까요?