Zig가 실제 CLI 도구 개발에서 Rust보다 더 실용적으로 느껴지는 이유
(dayvster.com)- 메모리 관리에서 Zig는 Rust보다 단순하고 직관적인 접근을 제공함
- Rust의 borrow checker는 강력하지만, 작은 CLI 도구 개발에는 과도한 복잡성과 개발자 부담을 유발함
- Zig의 수동 메모리 관리는 적절한 도구와 약간의 개발자 규율만으로도 효율적인 메모리 안정성 확보가 가능
- 프로그램의 안전성은 메모리 안정성 외에도 예측 가능한 동작, 관리 가능한 성능, 데이터 보호 등 다양한 요소가 중요함
- Rust는 대규모 시스템에 적합하지만, 작은 실용적 CLI 도구에는 Zig가 개발 생산성 및 유지보수 측면에서 더 유리함
개요
최근 CLI 도구를 만들 때 Rust보다는 Zig를 우선적으로 선택하고 있음
메모리 관리의 기본: 스택과 힙
- 스택은 함수 파라미터, 지역 변수, 반환 주소 등 매우 일시적인 데이터를 저장하는 빠르고 고정 크기의 메모리 영역임
- 힙은 동적 메모리 할당을 위한 영역으로, 데이터의 수명이 길거나 크기가 런타임에 결정될 때 사용함
- 스택은 구조적으로 간단하지만 공간이 제한적이고, 힙은 속도와 단편화 면에서 신경 쓸 점이 많음
Rust의 Borrow Checker
- Rust의 borrow checker는 컴파일 타임에 메모리 안전성을 보장함
- 참조, 소유권, 수명(lifetime) 같은 규칙을 강제해 null 포인터 역참조, 댕글링 포인터 등의 오류를 미연에 방지함
- 단, 메모리 안전성은 컴파일 타임 기준으로만 체크되며, 사용자의 실수나 복잡한 소유권 설계 문제 자체를 없애주지는 못함
사례: 나만의 Notes CLI
- 개인 노트 관리용 CLI를 Rust로 작성하려다 borrow checker 때문에 구조를 애써 다시 설계해야 했음
- 반면, Zig에서는 할당자만 사용해 포인터 기반 인덱스 생성 및 자유로운 변경/삭제가 훨씬 단순하게 가능함
- Rust의 borrow checker는 목적이 분명하지만, Zig는 기초적인 메모리 관리 지식과 규율만으로도 높은 수준의 효율과 안전성 확보가 가능함
메모리 안전성이 전부가 아닌 CLI 도구의 안전성
- 프로덕트의 진정한 안전성에는 예측 가능한 동작, 오류 발생 시 의미 있는 피드백, 민감 데이터 보호, 공격 내성 등 다양한 요소가 포함됨
- Rust나 Zig 모두 메모리 안전성 외 조건을 만족시키지 못하면 "안전"하다고 할 수 없음
- 예를 들어, CLI가 오류 상황에서 silent하게 자료를 덮어쓰거나 파일 권한을 잘못 설정한다면 사용자는 심각한 문제를 겪을 수 있음
-
CLI 도구의 안전성
- 예측 가능한 동작: 잘못된 입력이나 예기치 않은 상황에서도 일관되고 명확한 동작 보장 필요
- 충돌 및 데이터 손상 방지: 에러를 우아하게 처리해야 하며, 데이터 손상이나 안 알려진 충돌 방지 필요
- 성능 관리: 대량의 데이터를 처리해도 자원 소모나 응답성 저하가 발생하지 않아야 함
- 민감 정보 보호: 임시 파일, 권한 설정에 대한 주의 필요
- 공격 내성: 입력 검증, 메모리 오버플로, 주입 공격 등에서 강인함 필요
Rust Borrow Checker의 강점과 한계
-
강점
- 데이터 레이스 및 중복 참조 차단: 컴파일러가 단일 mutable 참조, 복수 immutable 참조 규약을 보장함
- 강력한 컴파일 타임 보증: 대부분의 메모리 관련 버그가 실행 전 차단됨
- 초기 버그 발견: 상용 서비스나 동시성 시스템에서 큰 이점이 됨
-
한계 및 불편함
- 인지적 오버헤드: 작은 CLI 작업에도 소유권/수명/참조 관리 고민이 필수로 따라옴
- 보일러플레이트/구조적 왜곡: Rc, RefCell 같은 래퍼, clone 남발, 구조 재설계 등 "문제 해결" 대신 "컴파일러 만족"에 집중하게 됨
- 논리적/상태적 버그엔 무기력: 메모리 규칙만 보장, 예측성/논리 오류/데이터 무결성은 보장하지 않음
- 엣지케이스 복잡도: 캐시, 글로벌 상태, mutable index 등에서 라이프타임 충돌이 쉽게 발생함
- 결과적으로, 작은 CLI 프로젝트에서는 Rust의 borrow checker가 개발자에게 "정신적 세금"이 되며, 실제 필요 이상으로 복잡해질 수 있음
Zig의 안전성과 단순성 접근
- Zig는 선택형 안전성 검사 및 수동 메모리 관리 기반임
- 할당자(allocator) 개념을 내장해 구조적, 예측 가능한 메모리 사용을 구현할 수 있음
- 커스텀 할당자도 만들어 자신의 프로젝트 특성에 맞게 메모리 관리 방식 지정 가능
- Zig의
defer
구문 덕분에 범위 종료 시 자동 릴리즈 및 리소스 클린업도 훨씬 직관적임 - Rust와 달리 개발자 책임이 강조되어 discipline이 필요하지만, 구조적으로 잘 설계하면 메모리 안전성 달성과 유지가 쉬움
- Zig는 코드가 간결하고, 포인터 및 리스트, 인덱스 등의 구조 변경이 Rust 대비 훨씬 단순
- Rust처럼 속박받지 않고도 동일한 수준의 안전·효율 코드를 구현 가능
- 추가로, Zig의 comptime 기능은 컴파일 타임 코드 실행, 테스트, 최적화에 큰 도움을 줌
개발자 경험(Developer Ergonomics)의 중요성
- 개발자 경험(ergonomics) 은 언어의 문법, 툴링, 문서화, 커뮤니티까지 아우르는 요소임
- Rust는 매우 엄격한 규칙 덕분에 궁극적으로 메모리 안전을 보장하지만, 지나친 규칙과 ceremony는 생산성을 저하시킴
- Zig는 개발자 주도적 설계가 강조되어 코드를 더 쉽고 빠르게 작성·수정·이해 가능함
- Zig는 직관적인 코드, 빠른 반복, 낮은 의식적 부담으로 개발자가 도구와 싸우지 않고 문제 해결에 집중하게 함
- Zig는 개발자를 신뢰하며 적절한 도구와 선택권을 주는 반면, Rust는 지나치게 감독적이고 제한적 느낌을 줄 수 있음
- 개발자를 "실수로부터 보호"하는 대신, 스스로 실수를 통해 배우며 발전할 기회를 보장하는 것이 개발자 친화적 환경임
결론
- 대형, 멀티스레드, 장기 실행 시스템 등 Rust의 장점이 극대화되는 분야에는 Rust가 여전히 최고의 선택임
- 그러나 작고 실질적인 CLI 도구에는 Zig의 경량성, 단순함, 빠른 구현·유지보수성이 더 적합함
- 메모리 안전성은 안전성 퍼즐의 일부분일 뿐이며, 예측 가능한 동작, 유지 관리성, 견고함 등 CLI 도구에 필수적인 요소들이 Zig에서는 더 쉽게 달성됨
- 결국 중요한 것은 ‘더 좋은 언어’가 아니라, 나에게 맞는 워크플로우와 프로젝트 특성에 적합한 ‘도구의 선택’ 임
- Zig는 "메모리 안전성 +낮은 의식적 비용 + 개발 친화성/생산성"이 결합된 작은 도구 개발에 완벽하게 들어맞는 언어임
Hacker News 의견
-
Zig의 장점은 C 개발자처럼 계속 생각할 수 있게 해주는 점임, 하지만 어느 정도는 단순히 익숙함의 문제라고 봄
-
Rust에 충분히 익숙해진 개발자들은 더 이상 borrow checker와 싸우지 않음, 이미 그런 코드 구조로 사고하게 됨
-
Rust에서 “객체 수프(object soup)” 같은 접근은 잘 먹히지 않지만, 사실 그게 근본적으로 더 쉬운 방법이라고 보진 않음, 단지 우리가 익숙해서 쉽게 느껴지는 것임
-
ergonomics(사용자 경험)는 측정하거나 수치화하기 어렵다는 전제를 받아들이면 위와 같은 논의가 계속 모호해짐
- "이 의자는 무너지지 않으니 안심하고 앉아라, 다만 조금 덜 편하고 무거울지 모르지만 대부분 사람들은 곧 익숙해져 불편을 못 느낄 것이다"라는 식의 이야기와 유사함
- 원글의 문장처럼 "Rust는 개발자 경험이 불편하지만 메모리 안전성을 주고, Zig는 더 나은 개발자 경험과 약간의 주의로 메모리 안전성을 줄 수 있다"는 것은 결국 안전성과 사용성의 교환임
- Rust 커뮤니티는 이런 트레이드오프를 솔직하게 인정해야 한다고 생각함, 안전성 확보는 언제나 덜 편리함을 수반함
- 보안, 생명, 일상, 소프트웨어 전반에 이 바탕이 깔려 있고, 주로 애매하거나 주관적인 주장들이 많음
- “익숙한 사람들은 문제가 없다”고 말하며 ergonomic 논점을 쉽게 무시하는 사람들도 있음, 이는 마치 “그것조차 힘들면 네가 덜 똑똑한 것”처럼 비칠 수 있음
-
“borrow checker와의 싸움”은 Rust에서 어휘적(Lexical) lifetime만 이해했던 시기에서 기원한 이야기임
- 내가 2021년에 Rust를 배울 때엔 이미 지나간 옛 얘기였음
- 실제로 파이썬, C, 자바스크립트 등만 써본 이들에게 Rust에 적응하는 건 쉽지 않을 수 있음
- 내 경우엔 쉽게 받아들여졌고, 대부분 그렇게는 느끼지 않는 것 같음
- 그런데 “Rust에선 진단 메시지를 읽으며 코드를 고쳐야만 한다”는 게 “용감하게 borrow checker와 싸운다”만큼 멋있게 들리지 않음, 실제 상황을 더 용감하게 표현해야 한다고 봄
-
내 경험상 숙련된 Rust 개발자들은 곳곳에 Arc를 뿌려서 자동으로 garbage collection과 비슷하게 씀
- 정적 메모리 관리가 꽤나 엄격해서 복잡한 자료구조에선 현실적으로 힘듦
- lifetimes 때문에 도달해야 하는 경로가 너무 사람의 인지 능력 이상임
-
숙련된 Rust 개발자도 Arc, Clone, Copy 등을 곳곳에 쓰는 오픈소스 Rust 프로젝트를 많이 봄
-
Zig의 장점은 C처럼 익숙하게 개발하면서도 언어나 툴링에서 안전성을 챙길 수 있는 기능들이 있음
- 예를 들어 Zig의 optional 때문에 nil deference 문제를 피할 수 있음
- 디버깅이나 테스트 시 debug/custom 할당자를 직접 전달해 런타임 검사, 메모리 접근, 리소스 누수 체크도 쉽게 가능
- 명시적 인터페이스/trait 부재엔 불만이 있지만, 채택이 단순해서 실용적이라고 생각함
-
-
나는 원글 내용에 대부분 동의하지 않음
-
Rust도 C나 Zig와 마찬가지로 lifetime, ownership, borrow scope에 대해 고민하게 되나, 차이는 컴파일러의 도움 유무임
-
아무리 똑똑해도 피곤하거나 산만할 때 실수하는 것이 인간임, 실수 인정이 곧 현명함임
-
Rust 컴파일러가 안전하다고 간주하는 프로그램의 공간이 충분히 넓지 않아, 꽤 자주 정상적인 프로그램을 거부함
-
예시: 구조체 Foo에서 bar와 baz가 각각 문자열일 때, bar를 가변 참조 후 baz를 불변 참조하려 하면 컴파일이 되지 않아, 이런 상황에서 코드 구조를 억지로 우회할 수밖에 없음
-
반론하자면, “실제론 괜찮은 상태인데 컴파일이 거부되는 경우를 피하기 위해” 두세 번째로 좋은 설계로 코드를 바꿔야 하는 것 자체가 큰 부담임
- 이렇게 하면 코드베이스 전체의 설계가 바뀔 수 있음
- 이게 게임 개발자 등 어려운 작업을 하는 사람들에게 Rust 채택이 부담스러운 이유로 설명됨
- 만약 Rust compiler가 거짓 경고 없이 정확히만 동작한다면 ergonomic 측면에서도 극강일 것임, 물론 현실은 쉽지 않음
-
위 예시는 정말 훌륭한 사례 같음, 내 블로그나 기사에서 다뤄도 될지 묻고 싶음
-
위 코드를 보고 오히려 주장에 더 자신이 없어짐
-
-
모든 프로그램이 반드시 그렇게 "안전"할 필요 없음을 우리가 기억해야 함
-
많은 Unsafe 소프트웨어를 즐기며 성장함, Star Fox 64, MS Paint, FruityLoops 등
-
Zig의 창시자인 Andrew Kelley도 음악 제작 소프트웨어(DAW) 개발 환경이 없어서 Zig를 만들었다고 읽음, Zig가 그런 창의적 소프트웨어에 잘 어울린다고 생각함
-
누구든 메모리 버그에 민감하면 Rust 쓰면 됨
-
Super Mario World도 메모리 버그 때문에 더 재밌었다고 믿음
-
"안전"이란 "내 프로그램이 의도한대로 동작함"의 축약어임
- 의도하지 않은 비논리적인 코드(semantic gibberish)는 목적 달성에 불리함
- 예술적으로 난해한 코드를 일부러 짜는 경우(IOCCC, 해킹, 코드 시)도 있지만, 이런 경우엔 신중하게 craft 해야 함
- Rust에서도 escape hatch(unsafe)로 의도적으로 이런 것 구현 가능
- 본문의 주장은, 본의 아니게 의미 없는(gibberish) 코딩이 장점이라는 것인데, 이는 쉽사리 동의하기 힘듦
- 만약 단점 없이 모든 코드를 안전하게 만들 수 있다면 누구나 원하지 않을까?
- Super Mario World의 스피드런 등은, 바이너리 패치로도 구현 가능하고, 입력을 통한 메모리 조작만이 유일한 재미라고 보진 않음
-
약간 혼동되는데, 내 말이 안 좋은 의견이라 생각하는 이유가 메모리 안전이 중요하지 않다는 의미인가 궁금함
-
-
borrow checker의 가치가 과소평가된 점이 아쉬웠음
-
Rust의 borrow checker는 유효하지 않은 메모리 접근을 컴파일 타임에 보장해 줌
-
물론, 컴파일러 규칙을 지키는 코드 구조로 바꿔야 하는 불편은 있음
-
직접 Rust를 따로 쓸 때 lifetime annotation이 틀렸다는 생각이 들진 않았고, 번거러운 chore 같긴 했으나 금방 익숙해졌음
-
unsafe를 쓰지 않는 한, Rust에선 두 스레드가 동시에 같은 메모리 쓰기 불가함
-
"왜 CLI 도구에서 Zig가 더 실용적으로 느껴지는지"는 공감이 안 됨, Rust가 CVE(취약점) 방지 면에선 여전히 강점임
-
실제로 나는 대부분 GC 언어로도 충분한 작업을 하고, 다른 언어로 기여할 땐 Rust, Zig, C/C++ 모두 상관없이 씀
-
CLI 도구가 특별할 게 없다고?
- 일반적으로 CLI 도구는 규모가 크지 않거나 개인 개발이 많아 관리가 쉬움
- Zig나 C는 커다란 코드베이스엔 잘 안 어울릴 수 있음, 더 복잡한 프로젝트에선 babysitter(보호자)가 필요
- 이와 비슷한 과거 논쟁으로 Java도“babysitter 언어”라고 불렸지만, 실상 많은 코드베이스에선 그게 필요함
-
unsafe를 안 쓴다고 두 스레드가 동시에 같은 메모리를 못 쓰는 건 아주 깔끔하게 떨어지지 않음
- 내가 원하는 컴파일러의 지원은 주로 메모리 접근 순서(memory ordering) 문제 해결임
- Rust는 이런 부분에서 race가 생기더라도 unsafe가 아니라 safe로 분류함
-
-
Rust에서 backlinks 구현이 너무 복잡하다는 점은 동의함
-
Rc, Weak, RefCell, .borrow() 등을 사용하면 가능하지만 쉽지 않음
-
짧게 실행되는 프로그램이라면 arena 할당도 방법임(CLI 도구가 뜻하는 바로 보임)
-
Rust는 거대한, 멀티스레드, 장시간 구동되는 어플리케이션에서 진짜 힘을 발휘함
-
내가 실제로 Rust로 대형 메타버스 클라이언트를 썼는데, 수십 개 쓰레드를 24시간 돌려도 메모리 누수, 크래시 없었음
-
같은 걸 C++로 하려면 QA팀과 Valgrind 같은 툴이 필수이고, 스크립트 언어는 성능상 너무 느림
-
나도 Rust로 지구 곡률과 중력 편차까지 반영한 물리 시뮬레이션 비행기를 만들었음
- 5시간, 22시간짜리 경로 테스트를 수년간 아무 문제 없이 돌렸음
- 7년간 Rust로 크래시 경험은 소수에 불과, C/C++는 과거 유산 코드 고칠 때만 씀
-
-
Zig가 매력적이긴 한데 D도 여전히 존재하고, 개인적으로 D가 내가 원하는 C/C++ 대체라고 느껴짐
-
Zig의 문법이 뭔가 어색하고, Rust는 이미 생태계에서 중심이 되고 있음
-
Go 역시 여러 언어 툴에서 점유율이 높고, AI 분야에선 Python 다음으로 많이 쓰임
-
Rust 이전에 Go vs. D 논쟁이 있었고, 나도 D 교재까지 샀었다가 Go에 넘어감
- 나에겐 표준 라이브러리가 훨씬 실용적이었고, int64 같은 자료형 이름이 더 직관적이었음
-
D가 좋긴 하지만 대중화될 킬러앱이 안 나옴
-
-
Rust나 Zig로 굳이 CLI 툴을 써야 할 이유를 잘 모르겠음
-
IO가 병목이지, GC가 느려서가 아님
-
GC 이슈는 게임, DB 등 메모리 집중형이 아니면 논점이 아니라고 생각함
-
메모리 안전성 논쟁보다는 GC가 필요 없는 언어를 선택해야 하는 이유를 더 고민하는 편이란 점을 강조함
-
만약 “노-GC가 재밌어서” 쓰는 거라면 그 자체로 충분함, 논쟁 필요 없음
-
즉각적인 스타트업 타임(실행 지연 없음)이 아주 유익함
- 래핑 툴 만들 땐 수천 번 돌려도 부담 없음
- Python 같은 환경의 배포 복잡성이 없어서 분배도 쉬워짐
-
Go로 CLI 만드는 게 매우 괜찮았음(비록 Go 언어 자체는 선호하지 않음)
- Python CLI는 의존성이 많으면 배포가 번거롭고, Rust/Zig도 Go처럼 정적 바이너리 배포가 쉬워 인기임
-
sum type, 패턴 매칭, async 지원이 있는 언어를 1순위로 고름
- Rust가 아니어도 에러를 컴파일 타임에 잡아주는 기능이 좋음
-
GC 없는 개발이 게임 분야에만 해당된다는 지적에 대해
- 실제로 많은 모바일 게임도 Unity+ il2cpp 환경처럼 GC를 사용하고, GC 성능도 좋지 않은 경우가 많음
-
GC 논쟁은 일종의 밴드왜건 느낌임
- 50년 전 이미 GC 기반 언어로 Interlisp, Cedar 등 대형 워크스테이션이 성공적으로 만들어졌음
- 오늘날 하드웨어 성능은 1970년대 CLI, Electron 앱보다 훨씬 좋은데 효율적 활용이 부족함
-
-
Rust의 빌트인 borrow/referencing으로 간단한 노트 툴을 만들었는데, 생각만큼 복잡하지 않았음
-
notes 리스트의 인덱스를 저장하고 맵으로 연결하는 구조를 상상하면, 속도 차이도 거의 없고, 안전성 단점도 없음
-
인덱스 실수해도 범위를 벗어난 에러로 잡을 수 있어, 커널 메모리를 덮어쓰는 것보다 훨씬 좋음
-
printf debugging 때도 훨씬 쉽고 직관적임
-
raw pointer나 reference는 보통 allocator, async runtime처럼 정말 필요한 곳에만 쓰고, 일반적 로직에는 index-based가 더 맞음
-
유명하게도 Rust async가 self-referential struct를 못 써서 Pin 관련 이슈가 생기는 이유도 여기에 있음
-
vec에 저장된 값의 포인터는 realloc 등이 일어나면 무효가 되므로, 이땐 Miri에서 바로 오류가 남
-
-
C++ 개발자인 내가 안전한 언어를 찾는다면 Swift가 제일 적합하다고 느낌
-
친숙하거나 닮은 언어가 더 금방 적응 가능함
-
Swift는 최근 크로스플랫폼 지원도 강화되었고, 현직 C++ 표준위원회 사람들이 여러명 참여함
-
하지만 Apple 연관성, 네이티브 UI 프레임워크 부재 등으로 비애플 진영 확장은 상대적으로 덜 함
-
Swift가 더 많이 인기 얻길 희망함
-
Swift와 Zig/C를 비교할 수 있는 리소스 있으면 추천해주면 좋겠음
-
-
Zig도 적당한 주의로 메모리 안전 소프트웨어를 만들 수 있다는 말이 있는데, 사실 C도 일정 수준 disciplined하게 쓰면 같은 결과가 나옴
-
결국 이 “조금의 절제(훈련)”가 현실에선 부족해서 문제가 발생함
-
Zig는 추가적으로 아래 문제들을 해결함
- Out of bounds access(전체 CVE의 70% 차지)
- null pointer dereference
- 타입 안전성
- C보다 월등하고, use-after-free 같은 오류도 경계를 안 넘는 한 훨씬 쉽게 피할 수 있음
- Zig의 뛰어난 크로스플랫폼 빌드 시스템, comptime 최적화, C++/Rust보다 수십 배 빠른 빌드 타임도 강점임
- 아직 표준 라이브러리가 미흡하고 자잘한 문제는 남았지만, perf-oriented 프로그램에선 미래가 밝다고 생각함
-
C가 50년 넘게 이런 discipline 문제로 실패했다면, "샤오린의 도" 이상으로 어려운 일임
-