C를 사랑하지만, 새 코드는 Zig로 쓰는 이유

Why Zig over C for Systems Programming

C는 UNIX의 언어이자 저자의 최애 언어지만, 새 코드를 C로 쓰고 싶지 않다고 말한다. Zig는 goto 없이 errdefer로 자원을 정리하고, 슬라이스가 길이를 스스로 알며, 에러를 무시하면 컴파일을 거부한다. comptime으로 전처리기를 대체하고, debug 모드에서 정수 오버플로와 범위 초과를 잡아낸다. zig cc로 C를 크로스 컴파일할 수 있다는 점도 강점이다. 다만 Zig 0.17 업그레이드 때 책의 152개 파일 중 35개를 고쳐야 했을 만큼 아직 불안정하고, C만큼 도구와 생태계가 넓지 않다는 한계도 인정한다.

나는 C가 나쁘다고 생각해서 이 글을 쓰는 게 아니다. C는 내가 가장 좋아하는 프로그래밍 언어다! 다만 더 이상 새 코드를 C로 쓰고 싶지 않아서 쓰는 것이다.
  1. uecker

    Zig는 좋은 언어지만, 그런 글들은 항상 현대적인 도구를 사용하지 않는 구식 C와 새로운 언어를 비교합니다. 나열된 장점 대부분은 현대적인 툴링과 스타일을 받아들일 의지만 있다면 C에서도 가질 수 있습니다: 부호 있는 오버플로 검사: sanitizers, 무시할 수 없는 에러: [[nodiscard]], 크기를 아는 슬라이스 타입: variably-modified types 또는 span 타입.

    Defer도 곧 C 컴파일러에 등장할 것이지만, 개인적으로는 여전히 goto를 사용한 에러 처리가 더 명확하다고 생각합니다.

    (Rust는 시간적 메모리 안전성에서 장점이 있지만, 이는 Zig에는 해당되지 않습니다.)

  2. variadix

    나는 오랫동안 C를 사용한 후 시스템 프로그래밍을 위해 C++의 한 방언을 정착시켰다. 그것은 대부분 C와 유사하고 예외, RTTI 등을 비활성화했지만 템플릿, 오버로드, 메서드, 람다 등은 사용한다.

    나는 생성자 메서드가 모두 상태 코드를 반환하는 나만의 객체 시스템을 가지고 있어서 예외가 필요 없고, 컨테이너, 버퍼링된 I/O, 네트워킹, 해싱, 수학, 스레딩, 비동기 등을 위한 나만의 stdlib도 있다.

    C++는 효과적으로 자신만의 언어를 구축할 수 있는 모든 기능을 제공하며, C++가 널리 지원되기 때문에 일류 툴링을 사용할 수 있다. LLM 덕분에 전체 stdlib을 혼자서 구축하고 테스트하기가 훨씬 쉬워졌다.

  3. asveikau

    첫 번째 예제만 봐도:

    struct user *user_new(const char *name, size_t n_groups)

    {

    struct user *u = malloc(sizeof *u);

    if (u == NULL)

    goto fail;

    u->name = strdup(name);

    if (u->name == NULL)

    goto fail_user;

    u->groups = calloc(n_groups, sizeof *u->groups);

    if (u->groups == NULL)

    goto fail_name;

    return u;

    fail_name:

    free(u->name);

    fail_user:

    free(u);

    fail:

    return NULL;

    }

    이보다 C를 더 잘 표현할 수 있다고 생각한다. 나는 하나의 정리 블록에 집중하고 free(NULL)이 no-op이라는 사실을 활용하는 것을 좋아한다.

    struct user *user_new(const char *name, size_t n_groups)

    {

    struct user *u = calloc(sizeof *u, 1);

    if (u == NULL)

    goto fail;

    u->name = strdup(name);

    if (u->name == NULL)

    goto fail;

    u->groups = calloc(n_groups, sizeof *u->groups);

    if (u->groups == NULL)

    goto fail;

    return u;

    fail:

    if (u)

    {

    free(u->name);

    free(u->groups);

    }

    free(u);

    return NULL;

    }

    비록 이것은 calloc(sizeof *u, 1)이 포인터를 NULL로 초기화한다고 가정한다는 점에 유의하라. 내 기억으로는 NULL의 비트 패턴이 모두 0이라고 가정하는 것은 기술적으로 표준에 어긋나지만, 대상으로 삼을 만한 모든 시스템에서는 여전히 작동할 것이다.

    "NULL 포인터를 free해도 괜찮다"는 패턴을 따르면 free 함수를 재사용할 수도 있다: […]

  4. pron

    또한 Zig는 적어도 C++만큼 표현력이 풍부하며(단, 어떤 작업이 수행되는지 정확히 봐야 하는 민감한 코드를 리뷰하기 어렵게 만드는 암시성과 일반적인 언어 복잡성은 없음), C/C++/Rust보다 아레나를 더 잘 지원한다.

  5. drnick1

    이 모든 새로운 언어들은 C에 비해 읽을 수 없다. 나처럼 for (size_t i = 0; i <= n; i++) total += a[i]; 같은 명시적인 것을 좋아한다면, 이 새로운 언어들은 모두 맞지 않을 것이다.

  6. kombine

    D와 Nim은 수년간 같은 기능을 가지고 있었다는 점도 참고하세요.

  7. qweqwe14

    Rust가 결국 이 모든 틈새 언어들을 대체할 것이다. 그렇다, Rust는 장기적으로 C와 C++를 대체할 수 있는 실행 가능한 대안이다. Zig는 단지 중간 지점을 노리는 것일 뿐인데, 우리는 그것이 통하지 않는다는 것을 확인했다. Zig는 LLM 시대에 Rust에 비해 특히 무의미하다.

  8. program

    > 무시할 수 없는 에러

    GCC와 Clang용으로 `__attribute__((warn_unused_result))` 매크로를 선언하고, 나중에 C 코드가 C++로 컴파일될 때(또는 아무것도 아닐 때) `[[nodiscard]]`를 사용하는 것은 나에게 괜찮다.

이 날의 다른 글

2026-11-13