한 Rust 개발자가 최근 커뮤니티 포럼에 Claude가 전적으로 생성한 코드 스니펫을 올렸다. 첫 시도에 컴파일이 됐다. 빌림 검사기(borrow checker)도 불만이 없었다. 라이프타임도 정확했다. 그런데 몇 분 만에 세 명의 숙련된 Rustacean이 이 코드가 기술적으로는 유효하지만, 생태계에 익숙한 사람이라면 절대 선택하지 않을 방식으로 문제를 풀고 있다고 지적했다. 안전한 추상화가 존재하는 곳에서 unsafe 블록을 사용했다. 빌릴 수 있는 데이터 구조를 복제(clone)했다. 잘 알려진 크레이트를 무시하고 기능을 처음부터 직접 구현했다.
코드는 맞았다. 하지만 *Rust*답지는 않았다.
컴파일되는 코드와 그 언어에 속하는 코드 사이의 이 구별 덕분에, Rust는 대형 언어 모델이 프로그래밍을 학습할 때 실제로 무엇을 배우는지 이해하기 위한 아마도 가장 드러내는 시험대가 되었다.
Rust의 컴파일러는 봐주는 법이 없다, 그래서 완벽한 AI 탐지기가 된다
대부분의 프로그래밍 언어는 많은 것을 넘어가 준다. Python은 정수가 와야 할 자리에 문자열을 넘겨도 런타임까지는 막지 않는다. JavaScript는 조용히 타입을 변환하고 그냥 진행한다. C는 배열 끝을 넘어서 쓰기를 해도 메모리를 오염시키면서 내내 웃고 있다.
Rust는 웃지 않는다. Rust의 컴파일러는 유명할 정도로, 때로는 화가 날 정도로 엄격하다. 소유권 규칙, 라이프타임 어노테이션, 빌림 검사기—이것들은 제안이 아니다. 벽이다. 그리고 생성된 모든 코드를 즉각적이고 이진적인 판정으로 바꿔 버린다: 통과하느냐, 아니냐.
Hacker News에서 Rust 기여자 및 메인테이너들의 논의에 따르면, 이 이진적 특성이 Rust를 독보적인 진단 도구로 만든다. LLM이 생성한 Python이 실행은 되지만 잘못된 결과를 내놓으면 몇 주 동안 알아채지 못할 수 있다. LLM이 생성한 Rust가 소유권을 잘못 이해하면 몇 초 만에 알 수 있다. 컴파일러는 버그만 잡는 게 아니다. *오해*를 잡는다.
여러 기여자가 현재 모델들, 특히 Claude와 GPT-4급 시스템들이 불과 1년 전에 비해 빌림 검사기를 통과하는 능력이 눈에 띄게 향상되었다고 언급했다. 간단하거나 중간 수준의 Rust 프로그램은 이제 첫 번째 또는 두 번째 시도에서 컴파일되는 경우가 더 많다. 하지만 커뮤니티는 컴파일이 Rust가 제시하는 가장 낮은 기준이지 가장 높은 기준이 아니라는 점을 재빨리 지적해 왔다.
문법적으로는 맞지만 문화적으로는 무지한: 관용적 표현의 격차
이렇게 생각해 보자: 프랑스어로 문법적으로는 완벽하면서도 실제 프랑스인이라면 절대 그렇게 말하지 않을 수 있다. Rust를 작성하는 LLM이 정확히 이 문제에 직면한다.
Rust 생태계는 강한 의견을 갖고 있다. 수동 루프보다 Iterator 체인을 선호한다. 패닉 대신 Result와 Option 타입을 사용한다. 자체적으로 직렬화, 비동기 런타임, 병렬 처리를 만들기보다 serde, tokio, rayon 같은 확립된 크레이트를 사용한다. 제로 코스트 추상화를 지향한다. 타입 시스템을 통해 불법적인 상태를 표현 불가능하게 만드는 코드를 작성한다.
이것들은 컴파일러가 강제하는 규칙이 아니다. 수천 건의 코드 리뷰, RFC 논의, 블로그 포스트에 담긴 문화적 지식이다. 그리고 바로 이 지점에서 LLM이 패턴 매칭과 진정한 이해 사이의 격차를 드러낸다.
Hacker News 스레드에서 한 Rust 메인테이너가 이렇게 표현했다: "모델은 무엇이 컴파일되는지 안다. 우리가 무엇을 머지할지는 모른다." 이 관찰은 LLM이 프로그래밍 언어를 처리하는 방식에 대한 근본적인 무언가를 꿰뚫는다. 모델은 방대한 코드 코퍼스로 훈련되었지만, 그 코퍼스에는 튜토리얼, 학생 과제, 품질이 들쭉날쭉한 Stack Overflow 답변, 그리고 구식 패턴이 포함되어 있다. 모델은 Rust 2015 관례와 Rust 2024 모범 사례를 구분하지 못한다. 본 것 전체의 평균을 낼 뿐이다.
그 결과, 생성되는 코드는 문법은 배웠지만 철학은 내면화하지 못한 사람이 작성한 Rust처럼 읽히는 경우가 많다. 빌림 검사기를 우회하기 위한 과도한 .clone() 호출. 제네릭으로 정적 디스패치를 얻을 수 있는 곳에서의 불필요한 Box<dyn Trait> 사용. ?로 에러를 전파해야 할 라이브러리 코드 곳곳에 뿌려진 unwrap().
AI가 물려받은 보장에 구멍을 뚫을 때
여기서부터 정말 흥미로워지며, 다른 언어 커뮤니티에서 보는 것과 Rust의 경험이 확연히 갈라지는 지점이다.
Rust는 특정한 약속을 하기 위해 설계되었다: 가비지 컬렉션 없는 메모리 안전성. 이 언어 전체가, 특정 부류의 버그—버퍼 오버플로, use-after-free 에러, 데이터 레이스—가 구조적으로 불가능해야 한다는 창시자들의 믿음 때문에 존재한다. 테스트로 잡는 것이 아니라. 코드 리뷰로 방지하는 것이 아니라. *불가능*해야 한다.
LLM이 컴파일되는 Rust 코드를 생성하면, 자동으로 이러한 보장을 물려받는다. 생성된 코드*는* 메모리 안전하다. 데이터 레이스가 *발생하지 않는다*. 컴파일러가 이를 검증했다. 좁지만 결정적으로 중요한 이 의미에서, AI가 생성한 Rust는 기본적으로 AI가 생성한 C나 C++보다 안전하다.
하지만 여러 커뮤니티 구성원들이 주목할 만한 반론을 제기했다. LLM은 숙련된 Rust 개발자보다 unsafe 블록에 더 쉽게 손을 대는 것으로 보인다. Hacker News 논의에서 여러 기여자가 모델이 해결하지 못한 빌림 검사기 에러를 우회하기 위해 unsafe를 사용하는 생성 코드를 보았다고 보고했는데, 이는 사실상 Rust를 선택하는 이유가 되는 바로 그 보장에 구멍을 뚫는 것이다.
이것은 기묘한 역전을 만든다. 언어의 안전 기능이 일종의 압력 밸브처럼 작동한다: 모델이 안전한 코드로 컴파일러를 만족시킬 만큼 소유권을 충분히 이해하지 못하면, 비상구가 있다. 그리고 unsafe를 거의 경외에 가까운 태도로 대하는 인간 개발자와 달리, LLM에게는 그 키워드의 무게감이 없다. 그저 또 하나의 토큰일 뿐이다.
Rust에서 "동작한다"가 잘못된 기준인 이유
표준 코딩 벤치마크—HumanEval, MBPP, SWE-bench—는 코드가 올바른 출력을 생성하는지를 측정하는 경향이 있다. 테스트를 통과하면 점수를 얻는다. 대부분의 언어에서 이것은 품질에 대한 합리적인 대리 지표다. Rust에서는 중요한 거의 모든 것을 놓친다.
벤치마크는 LLM이 동작하는 HTTP 서버를 만들었다고 점수를 줄 수 있다. 하지만 async를 올바르게 사용했는가? 역압(backpressure)을 처리했는가? await 지점에 걸쳐 실수로 잠금(lock)을 유지하고 있지 않은가—컴파일러가 일부 형태에서는 잡을 수 있지만 전부는 잡지 못하는 미묘한 동시성 버그인데? 잠금 없는(lock-free) 데이터 구조가 적절한 곳에서 Arc<Mutex<>>를 사용하고 있지 않은가?
일부 Rust 커뮤니티 구성원들은 "관용적 벤치마크"라고 부를 수 있는 것을 개발하기 시작했다—정확성을 넘어 생성된 코드가 커뮤니티 관례를 따르는지, 적절한 추상화를 사용하는지, 불필요한 할당을 피하는지를 측정하는 평가 기준이다. 이 작업은 아직 초기 단계이지만, AI 코드 생성을 순수하게 기능적 정확성만으로 평가하는 것은 외과의를 환자가 살아남았는지만으로 평가하는 것과 같다는 커지는 공감대를 반영한다. 생존은 필요하지만 충분하지 않다.
신뢰 위에 세워진 생태계에 대한 숨겨진 비용
여러 Rust 기여자들이 지적한 이차적 효과가 있는데, 다른 곳에서는 많이 논의되지 않은 것이다.
Rust의 생태계는 Python이나 JavaScript보다 작다. 커뮤니티는 긴밀하다. 크레이트 메인테이너들은 서로 알고 지내는 경우가 많다. 코드 리뷰 기준이 높다. 주요 크레이트에 대한 풀 리퀘스트는 정확성 검사뿐만 아니라 스타일, 문서화, API 설계 심사를 포함하는 엄격한 리뷰를 거친다.
들어오는 기여의 상당 부분이 AI가 생성한 것일 때 어떤 일이 벌어지는가? Hacker News 논의의 메인테이너들은 원칙적으로 AI 도구에 반대하지 않았지만, 두 가지에 대한 우려를 표했다. 첫째, 리뷰 부담: 컴파일은 되지만 관용적이지 않은 AI 생성 코드는 리뷰어의 일을 줄이는 것이 아니라 늘린다. 무엇이 잘못되었는지뿐만 아니라 *왜* Rust 커뮤니티가 다르게 하는지를 설명해야 하기 때문이다. 둘째, 신호 문제: 누군가가 풀 리퀘스트를 제출하면, 그것은 역사적으로 그 사람의 기술 수준과 이해도에 대한 정보를 전달해 왔다. AI가 생성한 제출물은 그 신호를 완전히 뒤섞어 버린다.
이것은 다른 커뮤니티에서도 제기된 코드 리뷰의 광범위한 도전을 반영하지만, Rust 사례는 새로운 차원을 더한다. 컴파일러가 리뷰어의 업무 일부를 자동으로 처리한다—메모리 안전성과 타입 정확성을 보장한다. 하지만 리뷰의 *인간적* 부분—설계 결정, API 인체공학, 관용적 사용법 평가—은 자동화할 수 없다. 그리고 바로 그 지점이 AI 생성 코드가 가장 많은 검토를 요구하는 곳이다.
실용주의자 vs. 순수주의자: Rust가 진정 무엇을 위한 것인지에 대한 논쟁
Rust 커뮤니티의 분열은 "AI 찬성"과 "AI 반대" 진영 사이에 있지 않다. 그보다 더 미묘하다.
한 진영, 실용주의자라고 부르자, Rust의 엄격한 컴파일러가 AI 지원 개발에 *이상적인* 언어로 만든다고 주장한다. 논리는 간단하다: 컴파일러가 위험한 에러를 잡으니, 상용구 코드에는 AI를 쓰고 인간의 주의력은 아키텍처와 설계에 집중할 수 있다. 빌림 검사기가 통과하면 이미 전체 범주의 버그를 제거한 것이다. 기계에게 문법을 맡기고, 인간이 의미론을 담당하면 된다.
다른 진영, 순수주의자는 이 견해가 Rust가 실제로 무엇을 위한 것인지를 과소평가한다고 우려한다. Rust는 단순히 메모리 버그를 방지하는 것이 아니다. *소유권과 자원에 대해 신중하게 사고하는 것*이다. 빌림 검사기를 만족시키는 행위는 프로그램의 구조에 대해 무언가를 가르쳐 준다. 이것을 LLM에게 떠넘기면 교육적 이점을 잃게 되고, 소유권 모델을 한 번도 완전히 이해하지 못한 코드를 갖게 된다. 프로덕션에서 무언가 잘못되면—그리고 반드시 잘못될 것이다—한 번도 구축하지 않은 정신적 모델을 디버깅해야 한다.
양쪽 모두 설득력 있는 주장을 한다. 그리고 이 긴장은 사라지지 않을 것이다.
컴파일은 이해가 아니다
Rust는 대부분의 조사에서 시장 점유율 2~3%를 맴도는 니치 언어일 수 있지만, 프로그래머로서의 AI의 한계를 이해하기 위한 렌즈로서는 그 비중을 훨씬 넘어선다.
핵심 통찰은 이것이다: 컴파일은 이해가 아니다. 엄격한 타입 검사기를 통과하는 코드를 생성하는 모델은 무언가 실질적인 것—구문적으로, 타입 이론적으로 유효한 프로그램을 만드는 능력—을 보여준 것이다. 하지만 *왜* 그 프로그램이 그렇게 구조화되어야 하는지에 대한 이해를 보여준 것은 아니다. 타입 시스템은 바닥이지 천장이 아니다.
이것은 Rust를 넘어서도 중요하다. AI 코딩 도구가 모든 언어에서 더 보편화됨에 따라, "동작한다"를 "잘 됐다"로 취급하려는 유혹은 커질 것이다. Rust는 "컴파일된다"와 "관용적이다" 사이의 격차를 너무나 뚜렷하게 보여줌으로써, 모든 언어 커뮤니티에 대한 경고 역할을 한다.
빌림 검사기는 LLM이 메모리를 이해하는지 알려줄 수 있다. 소프트웨어 엔지니어링을 이해하는지는 알려줄 수 없다.
현재 Rust 커뮤니티는 자신들이 가장 잘하는 것을 하고 있다: 열정적으로 논쟁하고, 도구를 만들고, 기준을 낮추기를 거부하는 것. AI 코딩 어시스턴트가 그 기준을 결국 넘을 수 있을지—정확성뿐만 아니라 장인 정신에서도—는 오늘날 AI 지원 개발에서 가장 중대한 열린 질문일 것이다.