C를 대체할 수 있는 현대적 대안 ‘지그’ 입문서
컨텐츠 정보
- 조회 398
본문
지그(Zig) 프로그래밍 언어가 조금씩 시스템 프로그래밍 영역으로 들어오고 있다. 아직 1.0 버전에 이르지 못한 상태임에도 이미 여러 프로덕션 프로젝트에서 사용되고 있으며, 다양한 좋은 아이디어와 품질에 대한 헌신으로 지지자를 끌어 모으고 있다.
앤드류 켈리가 2015년에 시작한 지그는 매우 활발한 프로젝트로, 차츰 임계 질량에 도달하고 있는 것으로 보인다. 원대하다고 할 만한 지그의 목표는 수십 년 동안 이식 가능한 저수준 언어의 표준으로 군림해온 C 프로그래밍 언어를 대체하는 것이다.
영기서는 지그의 설계 목표, 메모리와 오류 처리 방식, 조건부 컴파일, C 및 C++와의 상호운용성, 그리고 기사를 작성하는 시점 현재 최신 버전 기능 업데이트를 포함해서 지그에 대해 전반적으로 소개한다.
지그는 C를 대체할 수 있을까?
지그를 ‘저수준 시스템 언어(low-level systems language)’라고 하는데, 저수준은 무엇을 의미할까? 시스템 언어 역시 모호한 용어이긴 마찬가지다. 지그 소프트웨어 파운데이션(Zig Software Foundation)의 커뮤니티 담당 부사장인 로리스 크로는 지그에 대한 설명을 요청하자 “지그를 범용 프로그래밍 언어로 정의한다. 시스템 프로그래밍 용도로 뛰어난 것은 분명하지만 그 외에 임베디드 디바이스 프로그래밍, 웹어셈블리 타겟팅, 게임 개발, 그리고 일반적으로 고수준 언어로 처리되는 대부분의 작업에도 적합하기 때문”이라고 답했다.
C와 관련해서 보면 지그를 쉽게 이해할 수 있다. 즉, 가비지 컬렉션이 없고 포인터를 사용하는 이식 가능한 범용 언어다. 현재 거의 모든 프로그래밍 인프라는 그 방식은 다양해도 결국 C를 토대로 구축돼 있다. 자바, 자바스크립트, 파이썬과 같은 다른 프로그래밍 언어도 C를 기반으로 한다. 지그가 C의 원형적 대체제로 광범위하게 채택된다면 그 혜택은 상당할 것이다. C와 비슷하지만 더 안전하고 빠르고 버그가 적고 유지보수하기 쉬운 언어로의 발전이 산업 전체에 미칠 파급 효과를 상상해 보라.
지그의 설계 목표와 구문
지그는 개발자가 시스템 메모리를 직접 다룰 수 있도록 허용한다는 면에서 “기계에 근접한” 언어다. 작업에 최대한 최적화된 코드를 작성하기 위해 꼭 필요한 직접 메모리 할당은 C 계열의 언어, 러스트, 기타 저수준 시스템 언어의 공통된 특징이다. 지그는 이와 비슷한 기능을 제공하지만 여러 측면에서 이를 개선하는 데 목표를 둔다.
지그는 C보다 더 단순한 시스템 지향 언어로서 안전한 코드 작성과 쉬운 코드 수정을 구현하기 위해 노력한다. 또한 C와 비슷한 소프트웨어를 작성할 때 직면하는 거친 부분을 다듬어서 개발자 경험을 개선한다는 목표도 갖고 있다. 처음 보면 지그의 기능이 혁신적이라는 느낌을 받지 못할 수도 있다. 그러나 전체적으로 지그는 개발자가 더 쉽게 마스터하고 사용할 수 있는 더 안전한 플랫폼이다.
현재 지그는 노드.js(Node.js)의 대안으로 번.js(Bun.js) 자바스크립트 런타임을 구현하는 데 사용되고 있다. 번을 만든 제러드 서머는 “지그는 C와 비슷하지만 디버그 모드에서 메모리 안전성 기능이 더 뛰어나고 defer와 같은 현대적인 기능을 갖추고 있으며 comptime을 통해 컴파일 타임에 임의의 코드를 실행할 수 있다. 키워드의 수가 매우 적어서 C++나 러스트보다 배우기가 훨씬 더 쉽다”라고 말했다.
또한 지그는 타이거비틀(TigerBeetle) 데이터베이스를 위한 언어로도 사용된다. 타이거비틀은 지그를 선택한 이유에 대해 “지그는 기계어를 위한 DSL이며, 컴파일 타임 기능 덕분에 컴퓨터가 수행해야 할 작업을 직접적으로 표현하기가 매우 쉽다”라고 설명했다. 본질적으로 지그는 부가적인 구문을 최소화하면서 기계가 수행하는 작업을 매우 명확하게 기술할 수 있게 해주며, 이 과정에서 표현력도 잃지 않는다.
지그에는 타입에 대한 계약을 정의할 수 있는 인터페이스(또는 러스트의 트레잇과 비슷한 어떤 것)가 없다. 타이거비틀도 인정하듯 이로 인해 ‘선언 위치에’ 인터페이스가 없다. 타이거비틀 개발자에 따르면 이 기능은 종속성이 없는 환경에서는 중요도가 상대적으로 낮다. 대신 지그에서는 컴파일 타임에 컴파일러의 검사를 거치는 비슷한 종류의 제네릭 프로그래밍이 가능하다.
지그를 사용하는 다른 프로덕션 프로젝트에는 고스티(Ghostyy) 터미널 에뮬레이터와 우버(Uber)가 있는데, 우버는 빌드에 지그를 사용한다. 또한 지그를 사용해 파이썬 속도를 높이는 사례도 있다.
지그가 다른 대부분 언어와 다른 점은 기능의 수가 적다는 것인데, 이는 “특정 작업에 대해 그 작업을 수행하는 단 하나의 명확한 방법만 제공한다”라는 설계 목표에 따른 결과다. 지그 개발자들은 이 목표를 매우 중시해서, 한때 지그에는 for 루프도 없을 정도였다. 이 설계는 이후 바뀌어 지금은 지그에도 온전한 for 루프가 있다.
러스트를 주로 다루는 케빈 라이너는 “지그는 매우 작고 일관적이라서 몇 시간 만에 필요한 작업을 수행할 수 있을 만큼 이해할 수 있었다”라고 말했다. C 개발자 네이선 크래독도 같은 의견을 피력했다. 초점이 명확한 지그 구문의 특성은 프로그래머들 사이에서 좋은 평가를 받는 것으로 보인다.
지그의 메모리 처리 방식
지그의 특징 중 하나는 언어 자체에서 메모리 할당을 직접 처리하지 않는다는 점이다. C나 C++에서 볼 수 있는 malloc 키워드가 지그에는 없다. 대신 힙 접근은 표준 라이브러리에서 명시적으로 처리된다. 이 기능이 필요할 때는 Allocator 객체를 전달하면 된다. 이 방식은 라이브러리에서 메모리를 사용할 때를 명확히 드러내면서도 메모리를 처리하는 방식을 추상화하는 효과를 낸다. 어떤 종류의 할당자가 적절한지는 사용자의 클라이언트 코드가 결정한다.
메모리 접근을 명시적인 라이브러리 특성을 만든 의도는 숨겨진 메모리 할당을 방지하는 것이다. 이는 리소스가 제한된 환경이나 실시간 환경에 특히 유리하다. 어디서든 메모리 처리가 이뤄질 수 있는 언어 구문에서 메모리가 분리되므로 메모리 처리가 더 명시적으로 이뤄진다.
클라이언트 코드에서 API에 전달하는 할당자의 유형을 지정할 수 있도록 허용하면 코드는 타겟팅하는 환경을 기반으로 선택하게 된다. 즉, 라이브러리 코드가 더 명확하고 재사용이 가능해진다. 애플리케이션은 사용 중인 라이브러리가 정확히 언제 메모리에 접근하는지를 판단해서 런타임에 가장 적합한 유형의 할당자(임베디드, 서버, 웹어셈블리 등)를 전달할 수 있다.
예를 들어 지그 표준 라이브러리에는 page allocator라는 기본적인 할당자가 포함돼 있다. 이 할당자는 const allocator = std.heap.page_allocator;와 같은 형태로 운영체제에 메모리를 요청한다. 사용 가능한 할당자에 대한 자세한 내용은 지그 문서에서 볼 수 있다.
지그에는 버퍼 오버플로우를 방지하기 위한 안전 기능도 포함되며 메모리 누설을 감지하는 디버그 할당자도 제공된다.
지그의 조건부 컴파일
지그는 조건부 컴파일을 사용하므로 C와 같은 전처리기가 필요 없다. 따라서 지그에는 C나 C++와 같은 매크로도 없다. 설계 관점에서 지그 개발 팀은 전처리기를 언어가 가진 한계와 이를 극복하기 위한 허술한 임시방편으로 본다.
매크로 대신 지그에서는 어떤 코드가 컴파일 타임에 평가될 수 있는지를 컴파일러가 판단한다. 예를 들어 if 문은 가능하면 컴파일 타임에 죽은 분기를 제거한다. 지그는 #define을 사용해서 컴파일 타임 상수를 생성하는 대신 const 값을 이 방식으로 처리할 수 있는지 판단해서 실행한다. 결과적으로 코드를 읽고 작성하고 이해하기 쉽게 될 뿐만 아니라 최적화의 기회도 생긴다.
에릭 엥하임이 설명했듯이 지그는 컴파일 타임 계산을 부차적인 기능이 아닌 핵심 기능으로 사용한다. 덕분에 지그 개발자는 제네릭이나 템플릿에 대한 명시적인 지원이 없어도 제네릭 코드를 작성하고 메타 프로그래밍을 수행할 수 있다.
지그의 독특한 기능 중 하나는 컴파일 타임에 코드를 실행할 수 있게 해주는 comptime 키워드다. 개발자는 이를 통해 제네릭에 대한 타입을 강제하는 등의 작업을 수행할 수 있다.
C 및 C++와의 상호운용성
지그는 C/C++와 높은 수준의 상호운용성을 지원한다. 지그 문서에도 “C 코드와 상호작용할 수 없는 언어는 도태될 위험이 있다”라고 나와 있다.
지그는 C, C++를 컴파일할 수 있다. 또한 많은 플랫폼을 위한 libc 라이브러리를 기본 제공하는데, 외부 libc 라이브러리에 대한 링크 없이 빌드가 가능하다. 이 레딧 스레드에서 지그와 libc의 관계에 대한 세부적인 논의를 볼 수 있다.
앤드류 켈리의 블로그 게시물에서는 지그의 C 컴파일러 기능에 대한 심층적인 이야기를 볼 수 있으며 이 글에는 지그가 GCC LuaJIT 컴파일러를 컴파일하는 데모도 포함돼 있다. 요약하자면 지그는 단순히 C를 자체 구문으로 대체하는 것이 아니라 최대한 C를 흡수하려고 한다.
로리스는 “지그는 기본적으로 크로스 컴파일을 지원하고 그 외에도 여러 장점이 있어 다른 C/C++ 컴파일러보다 더 낫다. 또한 지그는 C와 간단히 상호작용할 수 있고(C 헤더 파일을 바로 가져올 수 있음), 강력한 타입 시스템과 defer와 같은 언어 기능 덕분에 C보다 C 라이브러리를 더 잘 활용한다”라고 설명했다.
지그의 오류 처리
지그의 오류 처리 시스템은 독특하다. ‘숨겨진 제어 흐름을 지양한다’는 설계 원칙에 따라 예외를 발생시키는 throw를 사용하지 않는다. throw 함수는 추적하기 어려운 방식으로 실행을 분기시킬 수 있기 때문이다. 대신 지그에서는 필요한 경우 문과 함수가 정상적인 반환값과 함께 유니언 타입의 일부로 오류 타입을 반환할 수 있다. 코드는 이 오류 객체를 사용해서 적절히 대응하거나 try 키워드를 사용해 오류를 상위로 전달할 수 있다.
오류 유니언 타입은 ! 구문을 사용한다. 지그 문서의 간단한 “Hello, world” 예제에서 이 개념이 어떻게 동작하는지 확인할 수 있다.
const std = @import("std");pub fn main() !void { const stdout = std.io.getStdOut().writer(); try stdout.print("Hello, {s}!n", .{"world"});}이 코드의 대부분은 따로 설명이 필요 없지만 !void 구문은 흥미롭다. 이 구문은 함수가 void 또는 오류 중 하나를 반환할 수 있음을 의미한다. 즉, main() 함수가 오류 없이 실행되면 아무것도 반환하지 않지만 오류가 발생하면 오류 조건을 설명하는 오류 객체를 반환한다.
try 키워드가 등장하는 줄에서 클라이언트 코드가 오류 객체를 어떻게 사용하는지 볼 수 있다. stdout.print가 오류를 반환할 수 있으므로 여기서 try 식은 오류가 main() 함수의 반환 값에 전달된다는 것을 의미한다.
지그 툴체인과 테스트
지그에는 빌드 툴도 포함돼 있다. 예를 들어 다음과 같은 명령을 사용해 위의 프로그램을 빌드하고 실행할 수 있다(이 부분도 지그 문서에서 발췌).
$ zig build-exe hello.zig$ ./helloHello, world!지그의 빌드 툴은 크로스 플랫폼 방식으로 작동하며 make, cmake와 같은 툴을 대체한다.
2025년 현재 지그의 빌드 시스템에는 통합 패키지 관리자가 표준 구성 파일인 build.zig.zon과 함께 포함돼 있다. 이 시스템은 지그 객체 표기법(ZON)인 .zon 파일 형식을 사용한다. 이 형식에 대해서는 여기에서 더 자세히 볼 수 있다. ZON은 지그의 JSON이라고 할 수 있다.
일반적으로 지그 프로그램에는 표준 명령줄로 충분하지만, 패키지 관리자는 제3자가 사용할 패키지를 제공해야 하는 경우나 다수의 항목을 빌드해야 하는 경우, 또는 빌드 프로세스에 많은 단계가 포함된 경우에 대처할 수 있게 해준다.
LLVM을 넘어 발전
지그는 원래 LLVM 툴셋을 기반으로 구축됐지만 이후 경로를 바꿔 LLVM을 선택적 구성요소로 만들었다. 이는 지그 언어의 이식성을 구현하고 독립적인 빌드를 더 원활하게 지원할 수 있게 해준다. 이 업데이트가 흥미로운 이유는 지그 툴체인이 백엔드 인프라의 많은 부분을 지그 자체로 구현하는 방향으로 나아가고 있음을 보여준다는 데 있다. 지그 개발자는 이를 ‘셀프 호스팅 지그’로 지칭한다.
즉, 한때 LLVM의 프론트엔드 언어 구문이었던 지그는 이제 그 프론트엔드를 흡수하고 타겟 시스템에 구현을 출력하기 위한 다양한 옵션을 사용할 수 있는 완전힌 툴 스택으로 발전한 것이다. 대부분의 타겟 운영체제에는 LLVM에 의존하지 않는 지그 백엔드가 있다. 다만 원하는 경우 여전히 LLVM을 사용할 수 있고, 일부 경우 C 호환성 때문에 LLVM이 필요할 수도 있다.
현재의 추세는 개발에는 더 빠른 빌드를 위해 자체 호스팅을 사용하고, 이후 가장 최적화된 런타임 프로덕션 빌드를 위해 LLVM으로 전환하는 방식이다.
지그 생태계 현황
지그에는 활발한 디스코드 커뮤니티와 깃허브 생태계가 있다. 내부 문서화도 면밀하게 이뤄진다. 또한 지그 사용자들이 만든 서드파티 자료도 상당한 수준이다.
지그는 여전히 비교적 초기의 프로젝트로, 이 기사를 작성하는 시점을 기준으로 현재 버전은 0.14.0이다. 느린 개발 속도는 팀이 품질에 전력하는 데 따른 결과다. 이미 여러 실제 프로젝트에서 지그를 프로덕션에 사용하고 있다. 로리스는 프로덕션 준비 상황과 관련해 “지그는 아직 v1.0에 이르지 않은 만큼 웹 개발과 같은 분야는 여전히 초기 단계에 있지만 개인적으로 지그 사용을 권장하지 않는 유일한 분야는 데이터 랭글링이다. 데이터 랭글링에는 파이썬, 줄리아와 같은 동적 언어가 더 실용적이라고 생각한다”라고 말했다.
웹 개발은 지그의 용도를 생각할 때 가장 먼저 떠오르는 분야는 아니겠지만 지그의 자인(Zine) 정적 사이트 생성기는 상당히 성숙한 단계이며, 현재 지그 웹사이트를 호스팅하고 있다. 또한 지그는 여러 웹 관련 프로젝트를 지원한다.
1.0 릴리스를 향한 진척 상황은 이 깃허브 이슈에서 추적할 수 있다. 목표 릴리스 날짜는 없지만 상당수 이슈는 중대한 수정보다는 편의적 요소에 가까운 것으로 보인다.
현재로서는 지그 팀은 충분히 시간을 두고 천천히 1.0 릴리스를 준비하는 모습이다. 1.0 버전은 2025년에 나올 수도 있고 더 이후가 될 수도 있지만 지금도 원하기만 하면 얼마든지 지그를 사용해 다양한 것을 구축할 수 있다. 지그는 활동과 목표, 개발자 커뮤니티에서의 인기 측면에서 흥미롭게 지켜볼 만한 프로젝트다.
dl-itworldkorea@foundryco.com
관련자료
-
링크
-
이전
-
다음





