Rust의 핵심
(jyn.dev)- Rust는 다양한 개념이 서로 긴밀히 얽혀 있는 언어로, 기본 프로그램을 이해하기 위해서도 많은 요소를 동시에 배워야 함
- 함수, 제네릭, 열거형, 패턴 매칭, 트레잇, 참조, 소유권,
Send/Sync
,Iterator
등은 모두 상호작용하며 설계된 핵심 요소임 - 자바스크립트와 비교했을 때, JS는 일부 개념만 알아도 코드를 작성할 수 있지만, Rust는 언어 전체의 맥락을 이해해야 비로소 의미 있는 코드 작성 가능
- Rust의 이 같은 복잡성은 학습 장벽을 높이지만, 동시에 안전성과 일관성을 제공하고, 코드 설계 방식에 큰 영향을 미침
- 이러한 언어적 짜임새는 Rust를 특별하게 만들며, “더 작은 Rust”의 비전은 정교하게 결합된 언어 철학을 다시 돌아보게 함
Rust 학습의 어려움
- Rust는 진입 장벽이 높음에도 불구하고 많은 사람들이 문서, API, 진단 개선에 기여해왔음
- 기본 개념으로는 함수 일급 객체, 열거형, 패턴 매칭, 제네릭, 트레잇, 참조, 빌림 검사기, 동시성 안전성, 반복자 등이 있음
- 이 개념들은 서로 의존하며 얽혀 있어 하나씩 따로 배우기 어렵고, 표준 라이브러리도 대부분 이 기능들을 활용함
- 20줄 남짓한 Rust 코드조차 이해하려면 함수형 패러다임,
Result
와 에러 처리, 제네릭 타입, 열거형, 반복자 등 여러 요소를 동시에 파악해야 함
Rust와 JavaScript 비교
- 동일한 파일 변경 감지 프로그램을 Rust와 JS로 작성했을 때, Rust는 다수의 언어적 개념이 얽혀 있음
- JS는 기본적으로 함수와 null 처리만 이해하면 충분히 동작하는 코드를 작성할 수 있음
- 이는 Rust가 단순히 더 어렵다는 의미가 아니라, Rust는 언어 전체의 구조적 이해를 요구하는 설계임을 보여줌
Rust의 상호 결합된 설계
- Rust의 핵심은 유기적으로 설계된 기능들의 결합임
- 열거형은 패턴 매칭 없이는 불편하고, 패턴 매칭도 열거형 없이는 제약적임
-
Result
와Iterator
는 제네릭 없이는 구현이 불가능함 -
Send/Sync
개념과println
제약은 트레잇이 있어야만 안전하게 표현 가능함 - 빌림 검사기는 클로저의 캡처 분석을 통해
Send/Sync
안전성을 보장함
- 이러한 상호 결합은 Rust를 단순히 기능의 집합이 아닌, 통합된 언어 체계로 만듦
작은 Rust의 비전
- 2019년 without.boats는 “Smaller Rust”를 언급하며 작고 정제된 Rust의 가능성을 논의했음
- 오늘날 Rust는 훨씬 커졌지만, 작은 Rust의 개념은 정교하게 맞물린 언어 설계의 본질을 상기시켜줌
- Rust의 매력은 언어적 요소가 서로 독립적이면서도 결합할 때 강력한 표현력과 안전성을 제공한다는 점임
결론
- Rust는 학습하기 어렵지만, 서로 얽힌 개념들의 일관성과 통합성이 큰 강점으로 작용함
- 이러한 구조 덕분에 Rust는 개발자가 코드를 단순히 작성하는 것이 아니라, 안전성과 성능을 동시에 고려하는 사고 방식을 갖게 만듦
- Rust의 본질은 “작고 정교한 핵심 언어”에 있으며, 이는 오늘날 확장된 Rust에서도 여전히 중요한 철학으로 남아 있음
Hacker News 의견
- "간단한" JS 프로그램에도 버그가 있음에 아이러니를 느낌. fs.watch 문서상 콜백에서 filename이 null일 수 있음을 반드시 체크해야 한다고 명시돼 있음. Rust라면 이 사실이 타입 시스템에 반영되어 무조건 처리하게 만들지만, JS에서는 대충 코드를 짜기 쉬움. 관련 문서
- Typescript를 사용하면 null 체크가 강제됨. 그래서 TS가 JS에서 Rust 쪽의 올바름에 좀 더 가까운, 비교적 부담 없는 단계임을 보여주는 좋은 예라고 생각함
- 추가적인 버그도 있음: for path in paths는 for (const path of paths)가 되어야함. JS는 괄호가 없으면 바로 에러를 내긴 하지만, in과 of의 차이는 값이 아닌 인덱스(iterable index)를 순회하므로, 실제로 인덱스가 string으로 변환되어 fs.watch의 첫 인자로 들어가 버림. 심지어 TypeScript도 이 실수를 캐치하지 못할 수 있음
- 지적대로 루프 문법 자체가 부정확한데, 실행만 해봐도 금방 알 수 있음. 즉, 작성자가 그 JS 코드를 신경써서 작성하지 않았고, 논점에 별 의미가 없었기 때문이라고 보는 게 나을 듯함
- 내가 못봤거나, kind가 어디서 온 것인지 궁금함. console.log("${kind} ${filename}")에서 kind가 아니라 eventType(문자열)이어야 맞음
- 사소한 지적 하나 하고 싶음. Rust의 println은 Display나 Debug 트레잇을 구현한 타입만 출력 가능함. 그래서 Path는 직접 출력 불가. 모든 OS가 UTF-8에 맞는 path를 저장하지 않고, Rust의 스트링 타입은 전부 UTF-8임. 즉 Path 출력은 손실이 발생할 수 있음. Path는 display 메서드로 Display 구현 타입을 반환함. Rust는 이를 타입 시스템에 녹였지만, JS/TS에서는 내부적 string이 UTF-16임을 명시하기 힘들고, Unicode가 아닌 path는 직접 TextEncoder/Decoder를 써야 제대로 다룰 수 있음. 예전 경험상 서버에서 Shift_JIS로 텍스트를 보내온 걸 response.text()로 읽으면 런타임에서 빈 문자열만 나옴. 인코딩 이슈에 익숙하지 않다면 이런 상황에서 디버깅으로 며칠 보낼 수 있음.<br>그리고 JS 예제는 Rust 코드에는 없는 버그와 문법 오류가 있음(루프에서 for-in 대신 for-of 필요). 이 예제에서 "일급 함수"만 쓴다고 보기는 어렵고, Rust처럼 이터레이터 이해도 필요하며, CommonJS를 쓰고 있음. 또 async/await, Promises 및 top-level await도 새롭게 배워야 하고, top-level await는 node 포함 일부 런타임에서 최근에야 지원됨. 여전히 일부 JS 엔진(예: React Native의 Hermes)에선 지원 안 함
- 이런 점이 내가 계속 Rust를 쓰는 이유임. 예제는 하나일 뿐이지만, 이런 자잘한 문제와 함정이 다른 언어엔 항상 널려있음. 개별적으로는 안 일어날 수도 있지만, 전체 프로그램 수명 주기에서 모이면, 어디서인지 계속 이상한 버그가 튀어나오고 끊임없이 찾아야 함. Rust에서는 이런 일이 발생하지 않음. 타입 시스템이 말도 안 되게 많은 경우를 미리 차단해 줌. 실제로 Rust로 기능 다 만든 소프트웨어를 출시하고 나면, 기능 추가만 가끔 했고 일반적인 버그 잡기 수고가 거의 사라짐. 물론 어디든 논리적 버그는 생길 수 있지만, 다른 언어처럼 어리석은 타입/구조 불일치에서 비롯된 문제를 원천 차단해주기 때문에 생산성과 유지관리가 완전히 다른 경험임
- 개인적으로 JS/TS에서 thenable/Promise와 async-await를 진짜 제대로 아는 개발자가 많이 없다고 느낌. 이런 것도 본 적 있음:<br>
콜백 형태 래퍼를 그대로 Promise로 감싸서 async 함수 안에서 다시 사용함. 이럴 때마다 너무 마음이 아픔. 실제로 이런 코드 곳곳에서 봄.<br>또, 모듈 import 및 async import(), 트랜스파일, 코드 스플리팅 등까지 생각하면 진짜 복잡함var fn = async (param) => new Promise((res, rej) => { fooLibraryCall(param).then(res).catch(rej); });
- Bjarne 인용문은 사실 C++이 점점 나빠지는 걸 반복적으로 정당화하기 위한 세일즈 피치라고 생각함. 초반엔 진심일 수도 있지만, 이제는 모델이 반복임. 구조는 이렇다고 봄:
- "C++ 안에는 더 작고 깔끔한 언어가 있음"
- 그런데 언어를 subset해서 뽑아낼 순 없으니, 먼저 superset(다기능화)를 만들고 그 후에 subset을 하자고 함
- superset은 새로운 C++N+1에 들어감. 진짜 subset 논의는 그다음에 한다면서 미루고 반복
- C++N+1은 더 복잡해지고, 이런 식으로 영원히 반복 반복적으로 이런 걸 본 사람들은 왜 계속 남아있는지 이해가 안 됨. 결국 "더 작고 깔끔한 언어"는 결코 안 나옴. 항상 step one 반복임
- xkcd 927과 닮았으면 떠오름 xkcd 927. C++ 표준 매번 점점 더 복잡해져서, 좋은 변화도 있지만 기존 버전과 잘 안 맞기도 하고 소스코드는 갈수록 엉망이 됨. 두 개 OSS 라이브러리 관리 중이지만 지금은 거의 안 씀. 언제까지 버텨야 할지가 요즘 고민임.<br>Rust는 c++11/14/17/20에서 넘어와서 진짜 상쾌함. 단, Rust도 전체를 다 모르면 충분히 방대함. 이번 글에서 지적한 내용이 매우 적절하게 느껴짐
- shebang(자체 실행 rust스크립트)을 보는 순간 바로 산만해졌던 사람 없음? 예전에 Go에서 똑같은 걸 발견했을 때처럼 놀람. 꽤 유용해 보여서 기본적인 용도로 충분히 쓰일 수 있을 듯. rust로 빌드/테스트 파이프라인 관리하는 프로젝트에서 비슷하게 본 적도 있음. 이런 용도엔 꽤 괜찮은 대안이 될 것임.<br>다만 나는 대체로 bash에서 조금만 벗어난 스크립트가 필요하면 Deno+TS를 씀. JS를 가장 오래(28년간) 다뤄왔고, 그다음 C# 24년임. Node 초창기부터 사용함. Deno가 패키지 공유/중앙화 측면에서 Node나 Python보다 더 관리하기 쉬움. cargo 프런트매터도 유사하게 동작함
- 내가 cargo에 script 통합을 직접 설계/구현했던 사람임(그간 서드파티 구현들도 많았음). 실사용 사례를 봐서 너무 기쁘고 언급된 걸 확인해서 좋았음. 문서도 참고 바람. 어떤 모양이 적합할지, 언어와 어떻게 연동할지, 첫 릴리즈의 범위는 어느 정도로 잡을지 등 지난한 논의가 있었음. 현재는 스타일 가이드와 Rust 레퍼런스 업데이트 등 마무리 작업 중이고, 남은 큰 일은 rustfmt, rust-analyzer 관련 디테일과 rustc의 버그 픽스 및 Cargo의 에러 리포팅 개선임. 나 스스로 매일 cargo script로 이슈 재현용 스크립트 작성함
- 실제로 "-Zscript" 기능 키워드 검색 시작해서 리서치하다가 산만해진 거임. 2023년부터 진행 중이고, 거의 완성에 가까워 보이는 오픈 이슈도 있음. ZomboDB 저장소에서 역시 rust로 빌드 파이프라인 처리하는 거 봤으나 전체 맥락은 완전히 이해하진 못했음.<br>cargo 프런트매터가 스크립트 이식성에서 엄청 유용함을 언급하고 싶음. 파일 하나만 공유하면 되고, Python이나 Node.js처럼 추가 설치/초기화 없이 바로 의존성 가져와 쓸 수 있음
- Go에서도 같은 걸 할 수 있다고 했는데 자세히 설명해 줄 수 있는지 궁금함. 관련 링크라면 나도 관심 있음
- JS와 C#을 오래 쓴 입장이지만, 2025년에 그런 이유로 어떤 시스템을 선택하는 건 별로임. 지난 20년에 정말 많은 것들이 훨씬 더 나아짐
- 그냥 기본적인 Unix의 기능일 뿐임. #!/some/path로 시작하는 파일은 그냥 셸에서 지정 명령어로 파일 전체를 stdin으로 넘겨서 실행함
- Rust에서 "더 작고 깔끔한 언어가 안에서 나오고 있다"는 말에서 정확히 그 언어가 뭔지 궁금함. 글을 보면 레퍼런스, 라이프타임, 트레잇, 열거형 등 다 남아있어야 동작한다는 뜻이고, 그러면 거의 Rust랑 다를 바 없음. 마지막 파트에서 "쓰고 싶은 Rust"와 "과거의 Rust" 두 가지 힌트가 나오는데 별로 와닿지 않음. withoutboats의 "Notes on a smaller Rust"도 읽었는데, 디자인 목표 자체가 Rust와 달라서 Rust가 되려는 게 아니라 새로운 언어 설계를 고민할 때 Rust에서 얻을 수 있는 교훈을 보여주는 정도임. Rust가 되고자 하는 언어가 아니라, "메인스트림" 요구에 맞춘 언어 사례(예: GC, 컴파일/문법 단순화 등)임. 두 번째로, "내가 2018년에 처음 배울 때 사랑에 빠졌던 언어가 그 '더 작은 Rust'"라는 얘기도 나오는데, 실제로 2018년 이후 Rust는 본질적으로 많이 안 바뀌었음. edition 변경 등 대부분 문법적 유연성 개선이고, 진짜 큰 예외는 async와 const뿐임. 그렇다면 "async와 const가 들어가기 전의 Rust가 더 작고 깔끔했다"고 말해주면 되는데, 본문에서는 그렇게 직접적으로 설명이 안 되어 아쉬움
- '더 작고 깔끔한 Rust'를 말한다면 Austral 언어가 예시로 떠오름
- Rust가 가진 핵심 개념을 간직하면서 더 단순한(더 작은) 언어가 가능하다는 주장도 있음. 예를 들어, Copy trait, reborrowing, deref coercion, 반복문에서 자동 into_iter, 범위 종료 시 자동 drop 호출(이것도 직접 호출하거나 컴파일러가 에러주면 됨), trait bound에서 기본 :Sized, 라이프타임 축약(lifetime elision), match ergonomic 등 여러 자동화/편의 요소들을 다 빼면 진짜 기계적으로 단순한 Rust가 가능함. 하지만 이런 언어는 일상적으로 쓰기엔 매우 불편함. 위 요소들은 사실 초보자를 위해 고안된 것들이란 점이 아이러니임
- 정말 꼼꼼하게 읽었음. 실제 내 의도도 Rust가 async와 const 도입 전이 더 작고 깨끗했다는 얘기임. 직설적으로 표현하지 않은 이유는 해당 기능 개발자 친구들이 많아서임. Matklad가 lobste.rs에서 아주 잘 표현해줌. 2015년 Rust가 더 완성형이고 일관성이 좋았으나, Rust의 비전은 완전한 일관성(coherence)이 아니라, 산업에서 쓸모있는 언어가 되는 것임
- 나는 편견이 있을 수 있지만 Rust가 가장 완벽에 가까운 언어라 생각함. 빌림 검사기(borrow checker)가 귀찮긴 하지만 꼭 필요함. 똑같이 버그 있는 코드(C)였다면 런타임 붕괴가 났을 것임—결국 그때도 버그는 고쳐야 함. 차이는 Rust는 아예 컴파일 전 미리 버그를 해결하게 강제하지만, C에서는 한밤중 오류에 수습을 하게 됨. Rust가 어렵다기보다 다른 사고방식의 전환을 요구함. 안전하고 보안성 높은 코드를 쓴다는 패러다임 쉬프트가 필요함. 변화는 대부분 불편하지만, 그게 Rust에 대한 거부감의 근본 원인인 듯함
- Rust는 완벽과는 거리가 있음
- Deref 적용 시점과 순서를 컴파일러가 너무 자유롭게 결정할 수 있다고 봄. .into()와 From 트레잇은 타입 변환을 너무 은밀하게 처리함. 표준 라이브러리에도 이런 "편의" 함수가 많음. 결국 객체의 타입이 모호해지고, 함수 호출과 구현 연결이 어려워짐(물론 IDE가 도와주면 조금 나음)
- 암시적 반환(implicit return)은 프로그램의 흐름을 가려서 실수를 불러옴. 물음표 연산자도 썩 맘에 들지 않음
- 작은 Rust 모듈을 너무 세분화해서, 유용한 걸 하려면 수백 개 디펜던시가 필요함. 각자 따로 관리하고 vendor해야 안정적인 빌드가 되는데, 이게 정말 불편함
- Async Rust는 지금 혼돈 그 자체임
- 빌림 검사기(borrow checker) 자체에 불만이라기보다, Rust 자체의 "덩어리"가 너무 커졌다는 게 주된 지적임. 2018년의 투박한(?) 불완전한 Rust를 좋아했던 사람들(나 포함)에겐 지금이 별로 매력적이지 않음. 물론 능숙하게 쓰면 아주 강력하지만, 정말 그만한 노력을 들일 가치가 있나 자문하게 됨. 2025년에 C/C++의 대안으로는 Zig를 고를 것 같음(유일한 예외는 Postgres 작업, pgrx 생태계는 아주 독보적임). 그래도 C로 일하는 것보단 뭐든 나음
- Rust는 완벽과는 거리가 있음
- 처음 배우는 언어로 Rust를 추천하면 안 된다고 생각함. 첫 언어 배우기는 원래 힘든데 Rust는 컴파일러 에러 때문에 코드가 완전히 완벽해질 때까지 실행조차 보기 힘듦. 너무 좌절스럽고 쉽게 포기할 것임. Python, JavaScript, Lua부터 시작해서 게임 같은 걸 빠르게 만들어보고 반복하는 게 낫다고 조언함
- 내 경험은 다름. 우리 회사 ML 엔지니어가 파이썬밖에 몰랐는데, Rust 코드베이스에 기여하고 싶어해서 내가 한 시간 정도 기초만 설명해줬더니 금세 잘 적응했고, 생산성도 바로 올라갔음. 사실 게임을 만들다가 문자열을 숫자 함수에 넘겨서 크래시 나면 원인 추적하는 게 시간 다 가는 일임. Rust에선 컴파일러가 아예 "여기 string인데 int여야 한다"고 표시해 주니까 차라리 디버깅 금방 끝남. 하루 종일 컴파일 에러 잡는 대신, 런타임 에러로 일주일 고생 안 함
- 블로그 맨 위 인용구의 본인임. 내가 Rust를 첫 언어로 400명 넘게 가르쳐봤는데, 이 스레드의 주장들이 매우 재밌게 느껴짐. 오랜 기간 직접 경험하면서 가능성뿐 아니라 꽤 잘 통한다는 근거도 충분히 얻음
- 난 아직 확신이 없음. Rust를 첫 언어로 좋은 교육자가 시도하는 걸 보고 싶음. 세대가 바뀌어 대학에서도 Python을 많이 쓰지만, 이론적으로는 Rust가 첫 언어로서 전체 코호트의 수준을 올릴 수도 있을 거라 생각함(물론 fail rate가 너무 높아서 행정상 문제 될 수도, 반대로 고급 학생은 더 많은 걸 익힐 수 있음). move assignment나 "const" 키워드 의미처럼, Rust에서 배우는 게 오히려 나중에 기존 언어들에서 배운 잘못된 습관을 걷어내야 하는 수고를 줄여주는 면도 있음
- 보통은 첫 언어로 정적 타이핑은 피하라고 권하고 싶음. 나도 정적 타이핑을 좋아하지만, 초보자 입장에선 괜히 혼란만 가중됨. 컴파일러 에러는 대게 반사실적이고, "컴파일러가 이게 none이 아닌지 증명하지 못했다" 식 메시지는 테스트 케이스에서 런타임 크래시로 바로 위치를 찾는 것보다 훨씬 어렵게 느껴짐. 직접 한 줄씩 출력값 찍으면서 트러블슈팅하면 대부분 금방 해결할 수 있는데, 컴파일러의 난해한 에러에 막히면 진짜 길게 헤맬 수 있음
- Rust는 한 번에 모든 걸 받아들일 수 있으면 나쁜 언어가 아님. 문제는 아무도 그런 식으로 언어를 배우지 않는 점이고, 주요 개념을 충분히 모르면 Rust에서 반복적으로 시행착오를 겪게 됨. 그리고 결국엔 다른 언어에선 전혀 배우지 않은 개념들도 많아서, 새로운 언어로 옮길 때 다시 좌절할 수 있음
- 내가 본 "간단한 Rust"의 가장 근접한 사례는 Gleam. Rust에 상당히 영감을 받은 듯함
- Gleam이 Rust에 영감을 받았다는 건 오해임. 제작자가 공식적으로 그렇게 말하지 않음. 컴파일러는 rust로 되어 있지만, Gleam은 패러다임이나 대상 런타임이 완전히 달라서 Rust 대체재는 아님
- 또 다른 'simple rust' 스타일을 원하면 fsharp를 살펴 볼 것도 추천함
- Gleam 메인 페이지에 흑인 인권, 트랜스권, 반나치 메시지가 있어서 나는 이 언어에 전혀 관심 없음
- Gleam에서 3D를 만들 수 있는지 궁금함
- "Rust 프로그램이 뭐하는 건지 설명을 안 한다"는 점이 신경 쓰였음. 엄청난 기술적 설명이 있는데, 프로그램이 실제로 뭘 하는지 요약이 없음. 실은 파일 변화 감지해서 프린트하는 것뿐임. Rust로는 이런 단순 작업도 구현은 복잡해져서, 실제 문제랑 관계없는 내부 세부사항까지 신경 써야 한다는 게 언어의 어려움을 잘 보여줌. 이 복잡함이 맞닥뜨릴 도전이자 동시에 자기주도적으로 만든 장벽이라 보는 입장임
- 다른 언어들도 똑같은 문제가 있지만, Rust는 그걸 미리 다루게 도와줌. 모든 파일명이 프린트 가능한 것은 아니고, 대부분 언어는 이 부분을 사용자한테 넘겨버림. Rust는 반환 타입으로 에러/실패를 분명히 표시하지만, 다른 언어는 예외처리 같은 다른 메커니즘이 필요함. 일견 단순해 보여도 실제로는 Rust 쪽이 더 직관적일 수도 있음
- 구현도 사실 고성능 언어치고는 매우 단순한 것임. 한 페이지 내에 다 들어감. 이 정도면 충분히 단순한 예제가 아님?
- 단순 설명 = 단순 구현이 항상 성립하지 않음. XKCD 1425에 사례가 잘 나옴. (예: 사진이 국립공원 안에서 찍혔는지 확인은 쉽지만, 그게 새 사진인지까지 구별하려면 연구팀이 필요함) xkcd 1425
- Rust는 의미적으로 꽤 일관되고 응집력 있다고 느낌. 다른 언어에 비해 당이 역할, 설탕 같은 게 적어서 더 직관적임. 모든 인터페이스는 보통 mem 모듈 패턴을 따르니, 인터페이스 구조를 확실하게 이해하려면 std::mem부터 시작하는 게 좋음