러스트 생태계의 과도한 추상화 성향을 유머러스하게 비판하며, 실용성과 단순함의 가치를 강조하는 글. 트레이트/제네릭으로 층층이 쌓인 라이브러리 문화의 장단과, 오늘을 위한 코드 쓰기, 그리고 "Go to Definition"이 유용하도록 유지하자는 원칙을 이야기한다.
10월 19일 | 작성: me 6분 읽기
러스트를 4년 써오면서 이 언어를 사랑하게 됐지만, 생태계가 추상화에 중독된 건 아닌가 하는 생각이 들기 시작했다. 또는: 왜 모든 러스트 크레이트가 추상화에 관한 연구 논문처럼 느껴지는가.
안녕, 또 나야. 또 하나의 "생각글"인 척하는 푸념을 들고 돌아왔어.
러스트로 뭔가 간단한 걸 만들어보고 싶었던 적 있나? 아마 bevy나 wgpu, 혹은 네가 원하지도 않은 모든 걸 약속하는 다른 라이브러리를 집어 들겠지.
그리고 그 순간이 찾아온다 — 디버깅하다가 "Go to Definition"을 누르면, 버퍼가 어떻게 업데이트되는지 알아내려고 트레이트, 매크로, 제네릭 열 겹을 자유낙하하게 되는 그 순간.
익숙하다면 축하해: 너는 러스트의 최애 취미 — 퍼포먼스 아트로서의 추상화 — 를 만났다.
이해해 — 추상화는 멋지다. 복잡함을 숨겨서 더 멋진 것에 집중할 수 있게 해주니까. 그리고 러스트는 그 아이디어를 사랑한다. 트레이트, 제네릭, 라이프타임 — "신경 쓰지 마"라는 층층의 레이어들.
nalgebra를 보자. 환상적인 크레이트 — 강력하고, 유연하며, 90%의 사용 사례엔 지나치게 과한 스펙이다.
nalgebra를 싫어하는 게 아니다 — 정말 훌륭하다. 다만 이런 식의 극대주의가 디폴트가 되어버렸단 게 싫을 뿐.
스크립트에서 살짝만 벗어나고 싶어?
그러면 트레이트 바운드 셋, 커스텀 derive 하나, 그리고 src/internal/utils/mod.rs로 떠나는 영적 여정이 기다릴 거다.
이런 라이브러리들이 이렇게 만들어진 이유가 사용자가 무엇을 원할지 우리가 모르기 때문이라고 말할 수도 있다 — 맞다, 그건 라이브러리 설계가 탄생한 이래의 저주다. 하지만 모든 문제가 추상화의 마천루를 필요로 하진 않는다; 대부분의 경우 필요한 건 그냥 작은 창고다.
예를 들어 glam은 철학을 풀려 하지 않는다 — 그냥 수학을 한다. Vec3::normalize()가 무엇을 하는지 이해하려고 제네릭 박사 학위가 필요하지 않고, 그게 바로 핵심이다.
하지만 문제는 — nalgebra가 고립된 예가 아니라는 점. 이건 문화다.
그리고 그게 추상화의 진짜 비용이다 — 천장을 높여주지만, 바닥을 보이지 않게 만든다. (셰익스피어 따위 날 못 이겨)
진짜 비극은? 그걸 한 번 보고 나면 너도 그렇게 쓰기 시작한다는 것. "음, 혹시 누가 행렬 대신 쿼터니언을 쓰고 싶어할지도 모르니까 제네릭으로 만들어야 하지 않을까..."라고 생각하다가, 축하해 — 존재하지도 않는 누군가를 위해 빌드하고 있다.
러스트는 훌륭한 언어다. 내가 제일 먼저 인정할 거다. 완벽하진 않다 — margarine만 빼고 어떤 언어도 완벽하진 않다 — 그래도 내가 제일 먼저 집어 드는 도구다.
그렇다고 해도, 러스트 생태계는 두 편으로 갈라진 것처럼 느껴진다. (아이러니하지?)
한쪽에는 아티스트들이 있다. 러스트를 예술처럼 대하는 사람들 — 모든 크레이트가 제네릭, 라이프타임, 제로-코스트 추상화의 걸작이다. 그들은 언어의 한계를 밀어붙이고, 솔직히? 보는 것만으로도 놀랍다.
다른 쪽에는 뭔가를 배송하려는 사람들이 있다. 문법 설탕에 알레르기라도 있는 Zig를 쓰고 싶어하는 그들. 우아함이나 영리한 추상화에는 관심 없다 — 단지 은퇴하기 전에 컴파일만 되면 된다.
말투가 거칠게 들릴지 모르지만, 사실은: 둘 다 틀리지 않았다.
한 가지 분명히 하자: 러스트에서의 오버엔지니어링은 엄청나게 재미있다. 언어가 반짝이는 도구들을 손에 쥐여주는데, 그걸 가지고 놀지 않기 어렵다. 프로그래머를 위한 레고 같다.
그리고 봐, 추상화의 경계를 밀어붙이면서도 빠르게 만들 수 있을 때? 우주의 코드를 풀어낸 기분이 든다.
그렇더라도; 대부분의 사람은 단지 코드가 무엇을 하는지 보고 싶어할 뿐, 트레이트 설치미술 속을 동굴탐험하고 싶진 않다.
핵심 문제는 러스트답게, 이해할 수 없는 코드가 디폴트가 되어버린다는 점이다. 커뮤니티는 네가 과하게 복잡하게 만들지 않으면 비러스터적(unidiomatic)이라고 말하러 찾아온다.
"Go to Definition"이 네 구현으로 안내하지 못하고, Matrix4::mul이 어떻게 동작하는지 보려고 네 GitHub 리포를 뒤져야 한다면 — 내가 사용 중인 코드를 정말로 이해한다고 말할 수 있을까?
많은 사람에게는, 어쩌면 그게 괜찮을 수도 있다. 하지만 네가 들여오는 모든 의존성은 결국 네 책임이다. 물론 내가 쓰는 모든 라이브러리를 전부 이해하진 못한다 — 그건 말도 안 된다 — 그래도, 내가 들여오는 코드는 이해할 수 있는 세상에서 살고 싶다.
그리고 분명히 하자: 추상화는 적이 아니다. 덕분에 하드웨어나 TCP 스택을 신경 쓰지 않고도 3D 엔진이나 HTTP 서버를 쓸 수 있다. 문제는 오늘이 아니라 언젠가(someday)를 위해 만들기 시작할 때다.
영리한 코드를 쓰지 말라는 말이 아니다 — 다만 그 코드가 제 값어치를 하게 하라는 것이다. "이걸 제네릭으로 만들까" 싶은 충동이 올라오면 물어봐라: 오늘, 누가 이득을 보지? 답이 "미래의 나"라면, 진짜 미래의 네가 나타날 때까지 기다려라.
내 엄지규칙은 이것 하나: "Go to Definition"이 유용하게 남아 있게 하라.
난 양 극단을 모두 경험했다. 심지어 OpenGL을 처음 배울 때, 배우면서 동시에 컴파일 타임 제로-코스트 추상화를 쓰려고도 했었다. 나중에는 그냥 날 것의 OpenGL로 여러 프로젝트를 했다. 그러니, 양쪽 모두 살아봤다.
그 경험에서 한 가지 배운 게 있다: 움직이는 부품이 적을수록 거의 언제나 더 쉽다. 더 단순한 코드가 더 나쁜 코드를 의미하진 않는다 — 단지 6개월 뒤에도 이해할 수 있다는 뜻이다.
그러니 아직이라면, 스펙트럼의 다른 쪽도 한 번 시도해봐. 아니면 말고.
아무튼, 이 글은 아마 끔찍하게 금방 낡아 보일 거다. 2년 뒤 누군가가 이 글을 내게 보내오겠지, 내가 매크로로만 작성한 트레이트 기반 ECS 라이브러리 여덟 번째를 만들고 있을 때 말이야.
하지만 지금은, 단지 한 사람이라도 추상화도, 트레이트도, 제네릭도 잔뜩 넣지 않은 — 그냥 코드인 코드를 써보자고 용기를 주고 싶었다.
언젠가 신기함이 사라지고, 러스트도 좀 진정하길.
그때까지는, 난 여기서 glam을 쓸 거다.
이 글이 마음에 들었다면, 팁이라도 주려던 마음으로— 글쎄, ko-fi를 아직 안 만들었네, 그러니 당장은 화면을 향해 'nice post'라고 속삭여주면 정말 기쁠 거야.
P.S. Grammarly 때문에 된통 당했어, 브로 🥀