1P by GN⁺ 2시간전 | ★ favorite | 댓글 1개
  • 약 3년 전 바이트코드 VM과 가비지 컬렉터를 Zig와 unsafe Rust로 직접 작성했을 때는 Zig의 인간 친화적 인체공학이 우세했으나, 코딩 에이전트 시대에 접어들며 그 우위가 사실상 무의미해짐
  • Zig의 주요 기능들이 제공하는 1.5~5배의 개발자 생산성 향상은, Rust 기반 코딩 에이전트가 제공하는 100배 생산성 향상에 압도됨
  • Zig의 allocator 인터페이스, 임의 비트 폭 정수, packed struct, comptime 등 핵심 기능은 모두 사람이 직접 코드를 작성할 때 빛나는 기능
  • Rust의 타입 시스템은 bounded polymorphism과 불변량 강제를 통해 에이전트의 실수를 컴파일 타임에 방지하는 데 더 효과적
  • 에이전트가 생성하는 코드량이 100배 증가한 상황에서 Rust의 메모리 안전성 보장은 Zig 대비 결정적 이점

핵심 변화

  • Zig는 unsafe 코드의 인체공학에서 큰 장점이 있었지만, 사람이 직접 코드를 쓰는 비중이 줄면서 그 장점의 실사용 가치도 작아짐
  • Zig 기능이 주는 1.5~5배 수준의 인간 개발자 생산성 향상Rust에서 코딩 에이전트를 쓰는 100배 향상에 가려짐
  • Zig의 대표 기능 상당수는 사람이 손으로 코드를 작성할 때의 편의성을 높이지만, 코딩 에이전트에는 그 차이가 크게 중요하지 않음
  • 약 3년 전에는 Zig와 unsafe Rust로 바이트코드 VM과 가비지 컬렉터를 작성하면서 unsafe 코드 작성 경험은 Zig 쪽이 낫다고 느꼈음
  • 2026년 기준으로도 Zig는 좋은 언어지만, Rust가 더 선호되고 코딩 에이전트와도 더 잘 맞는 언어로 바뀜

Zig의 할당자 인터페이스

  • Zig의 할당자 인터페이스는 특정 코드 경로를 최적화하기 위해 arena, stack fallback 같은 특화 할당자를 쉽게 적용하게 해줌
  • 사용자 입력 한 줄을 읽는 경우 입력 길이는 이론상 무제한이어서 힙 할당자가 필요하지만, 실제 입력은 대부분 짧은 검색어나 경로라 1KB보다 훨씬 작음
  • std.heap.stackFallback(256, heap_allocator)고정 크기 버퍼를 스택에 두고, 입력이 넘칠 때만 힙으로 넘어가게 해 일반적인 경우 힙 할당 없이 처리 가능함
  • 과거 Rust에는 Zig의 Allocator 인터페이스에 해당하는 기능이 없어, 커스텀 할당자를 쓰는 Vec<T>가 필요하면 표준 라이브러리 구현을 복사해 수정해야 했음
  • Bumpalo의 컬렉션 소스는 표준 컬렉션을 fork해 bump allocator에 연결한 형태였음
  • Rust nightly에는 한동안 Allocator trait가 있었고, 지금은 충분히 좋아 보이는 수준이 됨
  • Rust의 Allocator는 trait 기반이라 정적 디스패치를 쓰고, Zig의 할당자는 vtable 기반이라는 차이가 있음
  • Zig처럼 데이터 구조를 할당자 매개변수 기반으로 설계하는 커뮤니티 전반의 관례는 Rust에 없지만, AI가 코드를 복사해 바꾸기 쉬워지면서 이 한계가 덜 중요해짐

임의 비트 폭 정수와 packed struct

  • Zig의 임의 비트 폭 정수packed struct는 데이터 지향 설계식 CPU 캐시 최적화, tagged pointer, NaN boxing, bitflags 같은 작업을 쉽게 만들어줌
  • Obj-C API를 Metal과 함께 Objective-C runtime C API로 사용할 때, id는 정렬된 힙 객체 포인터가 아니라 tagged pointer일 수 있음
  • 정렬을 가정한 코드에 tagged NSNumber를 넘기면 UB가 발생할 수 있어, “힙 포인터인지 tagged immediate인지”를 저렴하게 검사해야 함
  • 단순화한 Objective-C tagged pointer 배치는 낮은 1비트가 “힙 포인터가 아님”을 나타내고, 다음 3비트가 클래스 슬롯을 식별하며, 나머지 60비트가 payload가 됨
  • Zig는 enum(u3)packed structclass: TaggedClass, payload: u60처럼 비트 레이아웃을 타입으로 직접 표현할 수 있음
  • Zig에서는 @bitCast로 원시 u64ObjcTaggedPointer를 오가며, is_ns_number에서 is_tagged.ns_number 클래스를 검사할 수 있음
  • Rust 대응 코드는 ObjcTaggedPointer(u64) 안에서 TAG_MASK, CLASS_MASK, CLASS_SHIFT, PAYLOAD_SHIFT 같은 상수를 두고, 생성 시 OR 연산을 하고 접근 시 마스크를 적용함
  • Rust에서는 클래스 슬롯이 실제 타입이 아니라 u64 상수이며, 손으로 쓰는 방식은 Zig보다 덜 인체공학적
  • Rust에서는 bitfieldbitflags 같은 crate를 쓰는 편이 낫지만, 둘 다 proc macro에 의존하고 Zig의 packed struct만큼 좋게 느껴지지는 않음
  • 코딩 에이전트가 있으면 이런 코드를 손으로 쓰기 귀찮다는 문제가 크게 줄어듦

comptime의 가치 변화

  • Zig의 comptime은 가장 화려한 기능이며, 일부 난해한 의존 타입 언어를 제외하면 Zig만큼 좋은 컴파일 타임 평가를 제공하는 언어가 거의 없다고 볼 수 있음
  • 실제 사용에서는 comptime을 많이 그리워하지 않게 됐고, 사용량의 약 95%는 매개변수화된 타입의 제네릭 데이터 구조를 만드는 데 쓰였음
  • fn ArrayList(comptime T: type) type처럼 타입을 받아 items: []T, capacity: usize, allocator: Allocator를 가진 구조체 타입을 반환하는 패턴이 대표적임
  • Rust의 타입 시스템은 Zig식 comptime 제네릭의 상당 부분을 대체하며, 더 많은 불변 조건을 강제할 수 있음
  • 나머지 약 5%의 경우에는 comptime이 없어서 불편하고, 신뢰할 만한 대체 수단은 codegen뿐임
  • 게임 개발 중 도구에서 생성된 hitbox geometry 데이터를 하드코딩해 자료구조에 넣고 싶을 때, Rust에서는 Claude에게 Rust 파일을 생성하는 스크립트를 작성하게 해야 함
  • 그래도 컴파일 타임 평가가 실제로 자주 필요하지는 않음

Rust 타입 시스템의 장점

  • Rust의 타입 시스템은 Zig의 comptime보다 더 가치 있는 교환 대상으로 평가되며, 특히 bounded polymorphism을 위한 traits/typeclasses 영역에서 강함
  • Zig에서 같은 수준의 bounded polymorphism을 구현하려 하면 매우 어려움
  • Rust 타입 시스템은 더 많은 불변 조건(invariant) 을 강제할 수 있어, 코딩 에이전트가 흔히 저지르는 실수를 막는 데 도움이 됨
  • 게임 코드에서는 euclid crate를 사용해 그래픽 프로그래밍에서 흔한 문제인 좌표 공간 혼동을 방지함
  • Point<Screen> 또는 Point<World>처럼 각 좌표 공간에 특화된 타입을 만들면, 월드 좌표와 스크린 좌표를 실수로 섞는 일이 컴파일 단계에서 막힘
  • WorldPoint, WorldVector, ScreenPoint를 별도 타입으로 두면 같은 공간의 point와 vector 덧셈은 허용됨
  • Translation2D::<f32, WorldSpace, ScreenSpace>를 통해 월드 공간에서 스크린 공간으로 명시적으로 변환할 수 있음
  • 반대로 let bad: ScreenPoint = player;처럼 WorldPointScreenPoint에 바로 대입하는 코드는 허용되지 않음

메모리 문제를 덜 다루는 효과

  • 코딩 에이전트가 100배 더 많은 코드 작성을 가능하게 하면, Zig 코드에서 메모리 문제를 검토해야 하는 양도 100배 늘어남
  • 형식 검증이 없다면 버그를 찾기 위해 살펴봐야 하는 탐색 공간의 표면적이 훨씬 커짐
  • 현재처럼 생성되는 코드의 양이 커진 상황에서는 Rust가 더 매력적임
  • Rust의 전통적 절충점은 borrow checker에 익숙하지 않을 때 개발자 생산성을 저해한다는 것이었지만, 코딩 에이전트가 있으면 이 단점의 중요성이 크게 줄어듦
  • Rust에서 unsafe를 쓰더라도 miri 같은 도구를 코딩 에이전트가 실행하게 해 UB가 발생하지 않는지, Rust의 aliasing 규칙을 위반하지 않는지 확인할 수 있음

결론

  • Zig는 여전히 그리운 언어이고 좋은 언어임
  • 2026년의 작업 방식에서는 Rust가 더 선호되며, 코딩 에이전트와의 궁합도 Rust가 더 좋음
Lobste.rs 의견들
  • 예전 팀 리드가 복붙 코드가 항상 나쁜 건 아니라는 꽤 강한 생각을 갖고 있었음
    DRY 원칙 때문에 본능적으로는 틀렸거나 논쟁적으로 들렸지만, 그는 매우 실용적인 사람이었고 주로 큰 테스트 코드베이스에 이 원칙을 적용했음
    똑똑한 공용 인터페이스를 억지로 만들기보다, 단순하지만 더 크고 중복이 많은 코드베이스가 유지보수하기 쉬울 수 있다는 논리였음
    요즘 LLM을 쓰면서 같은 생각으로 돌아가고 있는데, 이제는 더 중요한 소프트웨어 부분에도 적용하게 됨
    코드 생성은 빠르고, LLM도 단순하지만 중복이 많은 코드베이스에서 더 잘 맞힐 가능성이 있어 보임

    • 특히 테스트에서는 완전히 WET, 즉 “타이핑을 즐기자” 쪽임
      중복을 줄이려고 테스트에 추상화를 너무 많이 넣으면 테스트를 이해하기 어려워지고, 미묘하게 틀릴 위험도 있음
      더 나쁜 건 테스트 대상 코드의 추상화를 재사용하면, 테스트도 그 코드와 같은 방식으로 틀릴 수 있다는 점임
      또 애플리케이션 코드와 달리 테스트는 사실상 공짜로 “합성”됨
      테스트 하네스를 심각하게 망치지 않았다면 테스트를 마음대로 추가하거나 삭제해도 다른 테스트에 영향이 없고, 통합 마찰이 없으니 중복을 피해야 할 이유도 하나 줄어듦
    • 동의함
      테스트에서는 이걸 DAMP로 표현한 걸 본 적 있음: “Descriptive and Meaningful Phrases”로, 고유성보다 가독성을 강조하는 원칙임
      이 원칙은 비슷한 코드를 반복하는 식의 중복을 만들 수 있지만, 테스트가 더 명백하게 올바르게 보이게 해줌
      https://testing.googleblog.com/2019/12/…
      Go 커뮤니티에도 비슷한 말이 있음: “작은 복사는 작은 의존성보다 낫다” https://go-proverbs.github.io/
    • Repeat yourself, do more than one thing, and rewrite everything을 처음 읽었을 때 정말 와닿았음
    • 본능적으로 틀린 것처럼 들렸다는 부분은, 사실 애초에 틀린 적이 없었음
    • Andrew Kelley가 코딩하는 걸 보면서 좋은 교훈을 얻었음
      라이브 코딩을 공유해줘서 고맙게 생각함
      기억이 맞다면 뭔가를 시작할 때 자기가 만들려는 것과 가장 비슷한 코드를 찾아 통째로 복사한 뒤 거기서 수정하는 일이 자주 있었음
      “둘이 공유하는 추상화가 뭔지 앉아서 한참 생각하지 않는다고?” 싶었는데, 그냥 복붙하며 밀고 나가고 나보다 훨씬 생산적이었음
  • Bun의 Zig → Rust AI 재작성 타이밍을 생각하면 흥미로움 https://xcancel.com/jarredsumner/status/2053063524826620129#m

    • 최근 Bun에 병합된 PR 150개 중 108개가 메모리 안전성 관련이었고, 오류 경로에서 정리를 빠뜨리거나 use-after-free, 초기화되지 않은 읽기, 범위 밖 접근, 재진입 같은 문제였음
      그중 75개는 소멸자, 이동 의미론, 빌림 검사기가 있는 언어라면 컴파일되지 않았을 것이라고 함
      배포하는 PR 세 개 중 하나가 “오류 경로에서 해제를 깜박함”인 셈임
      108개 중 약 88개는 Zig에 있고, C++ 쪽 약 14개는 대부분 참조 순환과 GC 동시성 경쟁처럼 어떤 언어에서도 남는 잔여 범주라 함
      그래서 Zig→Rust 차이는 실제이며, Zig 버그는 정확히 소멸자와 소유권으로 고칠 수 있는 종류이고 C++ 쪽은 이미 바닥에 가까움
      더 강한 컴파일 타임 보장이 없으면 이건 계속 고양이와 쥐 게임으로 남음
      제안은 가장 큰 버그 범주를 계속 개별 수정하지 말고 구조적으로 제거하자는 것임
      bun/docs/rust-rewrite-plan.md at claude/phase-a-port · oven-sh/bun · GitHub
  • “나머지 5%의 경우에는 comptime이 없으면 괴롭고, 동등한 결과에 reliably 도달하는 유일한 방법은 코드 생성”이라는 부분은 저자가 무슨 뜻인지 명확하지 않음
    절차적 매크로에 대해 아무 말도 하지 않기 때문임

    • Zig의 comptime은 정말 멋지지만, Rust의 매크로 시스템도 절대 무시할 물건이 아님
      제대로 만들기는 좀 번거롭지만 많은 일을 할 수 있음
      코드 생성이 괜히 나쁜 평을 듣는 면도 있다고 봄
      build.rs 스크립트로 꽤 많은 짜증 나는 문제를 코드 생성으로 해결해왔고, 실행도 잘됨
      물론 나중에는 후회할지도 모르겠음
  • 글의 핵심 주장은 대략 이렇게 보임

    1. 자료구조별로 할당자를 커스터마이즈하는 특정 사례에서는 코드 복붙 비용이 어려운 문제였음
    2. Rust의 일부 타입 시스템 기능이 더 편리함. 다만 해당 예시에서 Rust의 타입 설계를 Zig로 포팅하고 comptime으로 API 형태를 강제하라고 에이전트에게 시킬 수 없다는 건 믿기 어려움
    3. 비트 플래그나 SoA 같은 기능에서는 코드 가독성이 중요하지 않음
    4. 컴파일이 메모리 안전성 오류의 부재를 보장한다면 “100배” 더 많은 코드를 리뷰할 수 있음
      Rust가 좋은 언어인 건 맞지만, 그래도 이건 좀 과함
  • 코딩 에이전트 광고처럼 보임

    • 코딩 에이전트가 프로그래밍 언어 선택에 어떤 영향을 주는지에 대한 솔직한 생각처럼 보임
    • 아니면 Zig를 위한 역심리 광고일 수도…
  • 같은 저자의 링크된 글에서 나온 내용임: 그래픽스 프로그래밍에서 아주 흔한 실수는 좌표 공간을 혼동하는 것이고, 타입 시스템은 어떤 좌표 공간과 변환이 유효한지를 타입으로 표현할 만큼 강력하다고 함
    통화, SI 단위와 야드파운드 단위 거리·무게, 검증된 문자열과 사용자 제공 문자열 및 비밀값 등에도 같은 얘기가 가능함
    또 잘 관리하면 상태 타입으로 불가능하거나 sound하지 않은 상태를 막을 수 있음
    하지만 개인적으로 Rust에서 가장 원하는 기능은 데이터 경쟁의 완전 제거
    관리형 언어에도 데이터 경쟁은 있음
    “그냥 Go 쓰라”는 Go에서는 모든 것이 스레드 사이에서 참조로 가변이고, 슬라이스 체조까지 붙음
    가장 순수하고 안전한 쪽에 속하던, 예전엔 완전 장난감 언어였던 JavaScript조차 모든 await가 잠재적 경쟁임
    everything-is-an-EventEmitter 패턴의 사악함은 제쳐두고 말임
    그래서 맞음. GC만 있었다면… 🤫

  • 핵심을 약간 숨기고 있는 느낌임
    코딩 에이전트는 Python과 JavaScript도 아주 잘 다룸
    Rust보다 나은지는 주관적으로 손을 흔드는 영역이지만, 그렇다고 많은 작업에 그 언어들을 고르지는 않을 것임
    문제는 Zig의 기능이 자주 바뀌는 데 있는 건지, 아니면 단순히 더 새 언어라서 AI 학습 데이터가 혼탁한 건지 궁금함

  • Zig는 Rust보다 쓰기는 더 어렵고 읽기는 더 쉬운 편이라고 느낌
    AI 시대에는 쓰는 코드보다 읽는 코드가 더 많아서, 나는 Zig 쪽을 선호하게 됨

  • 지금 생성되는 코드 양을 보면 Rust가 더 매력적이라는 말은 첫 단계일 뿐임
    컴퓨터가 코드를 더 많이 쓰게 될수록 더 형식적인 언어가 유리해질 것임
    동적 타입 논쟁의 또 다른 단계처럼 보임
    “동적 타입은 인간에게 더 쉬운데, 왜 기계를 위해 같은 걸 세 번씩 명시해야 하지?”라는 식임
    타입, 수명… 그 밖에 기계가 쓰고 소비하기 더 쉬운 것은 또 무엇일까
    미래의 컴퓨터가 코드를 작성하는 언어로 인간이 직접 코딩하기는 얼마나 어려워질지 궁금함