Rust에 대한 현재의 생각을 정리한 글이다. 안전성과 매크로 같은 장점부터 unsafe/panic, 문법적 설탕, 메모리 모델 집착, 재작성의 오류, 복잡성, 비동기 문제, 범용 언어로서의 사용에 이르기까지 비판적 관점을 제시한다.
2023-10-13: 피드백을 반영해 수정했지만, 견해 자체는 동일합니다.
이 글은 현재 내가 Rust에 대해 갖고 있는 생각을 간단히 정리한 것이다. 5년 뒤에 돌아보면 내 견해가 달라져 있을지 궁금하다.
이 언어에 전적으로 편향적인 것은 아니라는 점을 상냥한 독자께 보여드리기 위해, 분명히 긍정적으로 평가할 부분들도 있다:
unsafe의 사용은 다소 불안하다. 많은 라이브러리가 이를 사용하고, 사람들은 업무 프로젝트에서 언어의 한계를 재빨리 우회하려고 유혹을 받기 때문이다. 하지만 FFI를 쓰는 것과 크게 다르지도 않다. 이것을 큰 단점으로 보지는 않는다.
panic
은 좀 더 성가시다. Rust 라이브러리들은 에러를 명시적으로 처리하기 위해 ?
같은 많은 문법적 장치와 작은 에러 타입에서 큰 에러 타입으로의 자동 변환 등 큰 노력을 기울이지만, 정작 패닉은 스택을 프로세스 최상단까지 언와인드하고, 패닉 중에 또 패닉이 나면 소멸자가 실행되지 않는 등 문제가 있다. 종합적으로는, 언어 설계에 관한 나의 세 가지 질문에서처럼, “에러를 어떻게 처리하느냐”에 대한 답이 “적어도 서로 양립하지 않는 두 가지 방식”이 되는 셈이다.
Rust는 마법 같은 설탕(sugar) 구성 요소를 사용해 컴파일러가 자동으로 역참조와 복사, drop을 삽입해 준다. 처음에는 “속은 모두 단순하다”라는 매력이 있지만, 실제로는 좋지 않은 컴파일 오류로 이어진다. 최악의 컴파일 오류는 당신이 명시적으로 작성한 것이 아니라 컴파일러가 당신을 위해 생성한 것에 대해 컴파일러가 불평하는 경우다.
이는 Haskell이 모나드를 통해 제공하는 문법적 설탕과 비교할 수 있다. 마법을 많이 도입할수록, 초보자들이 배우고 대화하기가 더 어려워진다.
나는 Rust를 몇 년 써 온 사람들이, 자신들의 지극히 타당한 코드가 왜 Rust의 빡빡한 메모리 제약에 맞지 않는지 이해하려고 통화에서 20분을 소비하는 모습을 지켜봤다.
또한 머리가 희끗한 사람들이, 어떤 깨달음을 얻은 듯한 눈빛으로, 스택과 힙으로 이루어진 Rust의 메모리 모델을 “이해하고 나면”1 모든 것이 멋지게 맞아떨어진다고 나에게 Rust-설명(Rust-splain)을 한 적도 있다. 그 자체로 나쁠 것은 없지만, 반복되는 주제다.
이는 내가 다른 곳에서 다루고 싶은 또 다른 주제와 맞닿아 있다. 실천과 이론의 차이, 그리고 Rust와 Haskell처럼 큰 약속을 하고 큰 희생을 요구하는 언어의 사용자들이 그 차이를 구분하지 못하는 경향에 관한 것이다. 작동이 나쁜 것은 기술이 아니라 당신이 잘못 쓰고 있기 때문이다.
실제로 사람들은, 컴파일러와 체스를 두지 않고도 트리 같은 타입을 쓸 수 있기를 바란다. 나는 결국 Rust에서 트레이싱 가비지 컬렉터가 인기를 얻게 될 것이라 예측한다.
이는 Rust의 주요 목표이자—C처럼, 가비지 컬렉션 없이2 안전하게—동시에 가장 큰 단점이기도 하다. 사람들은 결코 차이를 만들지 않을 자잘한 일들에 시간을 낭비한다.
‘X를 Rust로 다시 썼더니 빨라졌다’라는 글을 많이 본다. 무엇이든 성능을 염두에 두고 처음부터 다시 쓰면 상당한 성능 향상을 보게 마련이라고 생각한다. Rust 자체가 얼마나 필요한지, 아니면 단지 개발자들이 성능에 대한 규율을 가졌기 때문인지를 나는 의심한다.
Rust는 이제 Haskell과 C++ 수준의 복잡성에 도달했다. 해마다 최신을 따라가려면 더 많은 지식이 필요하다. Go는 이런 식의 끝없는 언어 표면적 확대에 대한 해독제로 설계되었다. Rust에서는 기존 패턴(웹 서비스, 파서 등)을 재탕하는 라이브러리가 끝없이 나온다. 오랜 Haskeller로서 나는 그런 햄스터 바퀴를 15년 넘게 타 봤다. 한동안은 재미있지만, 어느 순간 지쳐 버린다. 이런 면이 Rust를 멀리하게 만든다. 나는 또 하나의 다마고치는 필요 없다.
새로운 언어 커뮤니티는 모두 다정하다. 중요하지 않을 때는, 사람들이 화낼 이유가 없다.
사람들이 어떤 것에 이해관계를 갖는 순간, 분위기가 달아오르고 성질이 드러난다. 프로그래밍 언어에 이해관계를 갖는 방법은 그 언어로 코드를 많이 쓰거나, 그 언어로 사업을 구축하는 것이다. 언어의 동작 방식에 이해관계가 생기면, 불필요하게 일을 늘리거나 목표를 제한하는 변화에 예민해진다.
이런 일이 Haskell에서도 벌어지는 것을 봤다. 2007년 무렵, 내가 Haskell을 시작했을 때, 커뮤니티는 그 무엇보다도 친절했고, 전도자 같았고, 개방적이었다. 사람들은 그 점을 칭찬했다. 모두가 이 흥미로운 언어를 쓸 수 있다는 사실만으로도 축복받았다고 느꼈다. 오늘날에는, 다른 어떤 커뮤니티와 크게 다르지 않다. 무슨 일이 있었나? 사람들이 Haskell을 진짜로 쓰기 시작했을 뿐이다.
Rust도 같은 일을 겪고 있는데, 훨씬 더 빠르게 진행 중이다. 이것이 Rust를 피해야 할 이유인가? 아니다. 하지만 “친절하고 환영하는” 커뮤니티는 떠오르는 언어에 합류해야 할 이유도 아니다.
이 글을 쓴 이후, 이미 사람들이 Rust를 포크하려 들고 있다. 거버넌스에 만족하지 않기 때문이다. 이는 위에서 말했듯 사람들이 이제 Rust를 사용하고 있다는 것 외에 깊은 의미는 없다. “친절하고 환영하는” 분위기 속에 더 다양한 기분과 동기가 뒤섞이기 시작했을 뿐이다.
언어 차원에서 공인된 런타임/스케줄러를 내장하지 않기로 한 Rust의 선택은, 언어 자체 안에서 대체 전략을 개발해야 함을 의미했다. 결과가 좋지 않다.
Coloured Functions는 비동기와 동기 함수의 비호환성, 그리고 그 둘을 섞을 때 생기는 어색함을 설명하는 강력한 은유다. 일부 블로그 글은 특정 문법 형태의 기술적 측면에 초점을 맞춰 이 문제를 과소평가하려 한다. 하지만 그건 별로 중요하지 않다. 현실은 훨씬 단순하기 때문이다. 의존성이 비동기 형태일수록 비동기 코드를 쓰기 쉽다.
사람들은 비동기인 라이브러리를 비동기가 아닌 라이브러리보다 선택할 것이다. 그러나 좋은, 유지되는 코드를 작성해 온 유지관리자들은 비동기를 채택하는 데에도 저항감을 갖는다.
사람 1: diesel과 sqlx의 또 다른 차이는 diesel은 아직 비동기가 아니고, 이슈를 보면 아직 우선순위도 아닌 것 같다는 점이야.
사람 2: 그건 큰 문제처럼 들리네
속담대로, 푸딩의 증거는 푸딩을 먹어 보면 안다. 비동기는 길고, 격한 논쟁을 불러온다.
Rust의 문제는 사용자들이 런타임을 원하면서도, 없는 선택지도 원한다는 데 있다. 결과는 난장판이다. 여기에 이터레이터까지 결합되면, 그런 코드를 이해하는 일은 꽤 어렵다고 본다.
일반적으로, 범용 용도라면 Go, Erlang, Haskell이 여기서는 더 나은 선택이라고 생각한다. 트레이싱 가비지 컬렉터와 그린 스레드는 범용 프로그래밍(시스템 프로그래밍이 아니라)에서 프로그래머의 생산성을 높여 준다. 다음 장으로 이어지는 이야기다.
Rust는 스스로를 “시스템” 언어라고 규정하지만, 실제로는 웹앱과 커맨드라인 도구 등 온갖 것들을 쓰는 데 사용되고 있다고 느낀다.
이는 조금 실망스럽지만, 동시에 예측 가능한 일이다. 언어가 더 성공할수록, 그 언어는 원래 의도하지 않았던 일에도 더 많이 사용되기 마련이다.
이 글은 여전히 자신의 정체성을 Rust에 결부시킨 많은 이들을 불쾌하게 하겠지만, 그건 그들의 문제이지 내 문제가 아니다.
위에서 밝힌 이유들로, 개인 프로젝트에는 Rust를 쓰지 않을 생각이다. 다만 이 글을 쓰던 당시 내 직장에서는 Rust를 사용하고 있었기에, 이에 대해 내 생각을 표현할 필요가 있었다.
하지만 단일 스레드 C의 대체물로, 표준 라이브러리만 써서 Rust를 사용하는 건 마다하지 않겠다. 그건 재미있을 수도 있다. 다만 나는 임베디드 작업은 하지 않으니 큰 기대는 하지 않는다.
훌륭한 도구 체인과 개발 팀이, 빅테크의 지원을 받는 Rust가 사람들의 눈을 가려, 이 언어가 단순하고 투자할 가치가 있다고 믿게 만든다고 생각한다. 그런 사고방식에는 위험이 있다.
C 코드를 꽤 해 본 입장에서는, 여기엔 내게 새로운 게 없다.↩︎
물론 Rc
와 Arc
는 참조 카운터(reference counting)로, 가비지 컬렉션의 한 형태다. 1996년에 출간된 _The Garbage Collection Handbook: The Art of Automatic Memory Management_를 보라. 하지만 대부분의 개발자는 가비지 컬렉션을 기술적 주제로서 피상적으로 이해하기 때문에, 그들에게 “가비지 컬렉션”은 “트레이싱 가비지 컬렉터”를 의미한다. 그래서 “가비지 컬렉션 대 참조 카운팅”을 겨루는 기묘한 논쟁이 벌어진다. 이는 마치 과일과 사과를 비교하는 것과 같다.↩︎