본문 바로가기
리눅스

(번역) Zig (programming language)

by 다움위키 2025. 2. 9.

원문 보기: https://dawoum.duckdns.org/wiki/Zig_(programming_language)

 

Zig (Ziglang이라고도 알려져 있음)는 Andrew Kelley에 의해 설계된 명령적, 일반적인-목적, 정적으로 입력된, 컴파일된 시스템 프로그래밍 언어입니다. 그것은 MIT 라이선스에 따라 출시된 자유와 오픈-소스 소프트웨어입니다.

그 언어의 주요 목표는 C 언어를 향상시키는 것이며, (역시, Rust에서 영감을 얻었으며), 그것에서 프로그램하기 더 작고 더 단순하지만 더 많은 기능을 제공하려는 의도를 가집니다. 언어 단순성에서 개선은 흐름 제어, 함수 호출, 라이브러리 수입, 변수 선언, 및 유니코드 지원과 관련이 있습니다. 게다가, 그 언어는 매크로 또는 전처리 지침을 사용하지 않습니다. 현대 언어에서 채택된 특색은 컴파일 시간 일반 프로그래밍 데이터 유형의 추가를 포함하여, 다양한 데이터에서 기능을 수행하도록 허용하여, 작은 새로운 컴파일러 지시문의 모음과 함께 반사 프로그래밍을 사용하여 해당 유형에 대한 정보에 접근을 허용합니다. C와 마찬가지로, Zig는 쓰레기 수집을 생략하고, 수동 메모리 관리를 가지고 있습니다. 그러한 시스템에서 발생하는 잠재적 오류의 제거를 돕기 위해, 그것은 옵션 유형, 그것들을 사용하는 데 간단한 구문, 및 언어에 내장된 단위 테스 프레임워크를 포함합니다. Zig는 -수준 프로그래밍, 특히 포장된 스트러크 (필드 사이의 패딩 없이 스트러크), 임의적인-폭 정수, 및 다중 포인터 유형에 대한 많은 기능을 가지고 있습니다.

이 시스템의 주요 단점은, 비록 Zig가 2025년 기준 점점 커지는 커뮤니티를 가지고 있을지라도, 여전히 성숙도, 생태계, 및 툴링이 개선될 영역을 가진 새로운 언어로 남아 있다는 것입니다. 역시 Zig의 학습 곡선은 가파르고, 특히 저-수준 프로그래밍 개념에 익숙하지 않은 사람들에게는 가파를 수 있습니다. 학습 자원의 가용성은 복잡한 사용 사례에 대해 제한적이지만, 관심과 채택이 증가함에 따라 점차 개선되고 있습니다. 검토자에 의해 언급된 다른 과제는 다른 언어와 상호 운용성 (데이터 마샬링 및 커뮤니케이션을 관리하기 위한 추가 노력이 필요함)뿐만 아니라, 수동 메모리 거래 (메모리 누출에서 직접 적절한 메모리 관리 결과를 무시함)입니다.

그 개발은 앤드류 켈리(Andrew Kelley)와 함께 비영리 단체, Zig Software Foundation (ZSF)에 의해 자금을 지원하며, 기부금을 받아들이고 여러 풀 타임 직원을 고용하고 있습니다. Zig는 매우 활발한 기고자 커뮤니티를 보유하고 있고, 여전히 초기 개발 단계에 있습니다. 그럼에도 불구하고, 2024년 스택 오버플로우 설문 조사에 따르면 Zig 소프트웨어 개발자는 평균적으로 연간 $103,000 USD의 급여를 받는 것으로 나타났으며, 이는 가장 많이 지불하는 프로그래밍 언어 중 하나입니다. 어쨌든, 0.83%만이 Zig에 능숙하다고 보고되었습니다.

Introduction

또 다른 터미널 에뮬레이터, Ghostty를 컴파일하기 위해 이 프로그램이 필요합니다. 물론 데비안 패키지 제작에서는 zig 바이너리를 받아서 그것으로부터 컴파일을 하기 때문에 시스템에 설치된 zip가 반드시 필요한 것은 아닙니다.

어쨌든, 패키지를 만들려고 합니다.

Installation

데비안에서 몇 년 전부터 패키징에 대한 의견이 있었습니다:

아직 데비안 저장소에 등록되지는 않았지만, 현재까지의 작업 결과, 0.13에 대한 소스 패키지와 바이너리 패키지를 아래에서 볼 수 있습니다:

17분 정도 걸려서 패키징이 완료됩니다.

Binary deb packaging

패키지에 대한 위의 정보를 알기 전에 만든 패키지입니다.

데비안 저장소에서 패키지를 제공하지 않기 때문에, 패키지를 제작해 볼 수 있습니다.

홈페이지 다운로드 정보에 따라서 0.13.0 버전의 소스를 받습니다:

그런-다음 아치 패키지에서 컴파일 관련 정보를 얻습니다:

다음 정보를 이용하십시오:

  • 버전 0.14.0-dev는 llvm-19에서 오류없이 진행되지만, Ghostty가 컴파일이 되지 않습니다. 여기서도 경로 오류가 생깁니다: libz, libzstd, libxml2. 아치 패치는 하나는 적용되어 있습니다.
  • 버전 0.13.0은 llvm-19에서 오류 발생하므로, llvm-18로 컴파일을 시도할 수 있습니다. 경로 오류가 생기는 libz, libzstd, libxml2, libtinfo를 /usr/lib/x86_64-linux-gnu 디렉토리에서 찾아서 /usr/lib/llvm-18/lib 디렉토리로 복사합니다. 추후 올바른 방법으로 처리되어야 할 것으로 보입니다.
  • 컴파일 의존성을 만족시키기 위해, liblld-18-dev를 설치해야 합니다.
  • 아치 패치 2개를 적용했습니다.

개발자가 제공하는 0.13.0 소스 파일을 풀어서, 아치 패키징에서 사용하는 패치를 2개 적용합니다.

그런-다음 아치 패키징 정보로부터 다음과 같은 zig.compile 파일을 소스를 푼 디렉토리에 만듭니다:

#!/bin/bash

cmake_vars=(
    CMAKE_INSTALL_PREFIX=/usr
    CMAKE_PREFIX_PATH=/usr/lib/llvm-18

    # The zig CMakeLists uses build type Debug if not set
    # override it back to None so makepkg env vars are respected
    CMAKE_BUILD_TYPE=None

    ZIG_PIE=ON
    ZIG_SHARED_LLVM=ON
    ZIG_USE_LLVM_CONFIG=ON

    ZIG_TARGET_TRIPLE=native-linux.6.1-gnu.2.38
    ZIG_TARGET_MCPU=baseline
)
cmake -B build "${cmake_vars[@]/#/-D}" .
cmake --build build

실행합니다:

  • bash zig.compile

시간이 꽤 걸립니다.

그런-다음 설치 스크립트, zig.install를 만듭니다:

#!/bin/bash

pkgdir=zig_0.13.0-1_amd64

install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"     

DESTDIR="$pkgdir" cmake --install build

mkdir -p "$pkgdir/DEBIAN"

실행합니다:

  • bash zig.install

그런-다음 Building binary deb packages를 읽고 control 파일을 적당히 만듭니다:

  • dpkg-deb --build --root-owner-group zig_0.13.0-1_amd64
  • sudo dpkg -i zig_0.13.0-1_amd64.deb

Language

Goals

Zig의 주요 목표는 현재 C로 해결된 임무의 종류에 대한 더 나은 해결책이 되는 것입니다. 그 점에서 주요 관심사는 가독성입니다; Zig는 기존 개념과 구문을 가능한 곳마다 유사한 개념에 대한 다른 구문의 추가를 피하도록 시도합니다. 게다가, 그것은 안전성, 최적화, 및 테스팅을 개선하기 위한 다양한 기능을 포함하여 "견고성, 최적성, 및 유지 보수성"을 위해 설계되었습니다. 작고 간단한 구문은 유지 보수의 중요한 부분인데, 왜냐하면 관리자가 익숙하지 않을 수 있는 언어의 복잡성을 배울 필요없이 그들이 코드를 디버깅하는 것이 그 언어의 목표이기 때문입니다. 이들 변경 사항에도, Zig는 기존 C 코드로 그리고 코드에 대해 컴파일할 수 있습니다; C 헤더는 Zig 프로젝트에 포함될 수 있고 해당 기능이 호출되고, Zig 코드는 컴파일러-제작 헤더를 포함함으로써 C 프로젝트에 링크될 수 있습니다.

코드를 간단하고 쉽게 읽을 수 있도록 하는 전반적인 설계 철학에 따라, Zig 시스템은 C 및 다른 C-계열 언어에 비해 많은 문체 변화를 포함합니다. 예를 들어, Rust 언어는 연산자 오버로딩을 가지며, 이는 a = b + c와 같은 명령문이 실제로 유형의 오버로딩된 버전의 더하기 연산자에 대한 함수 호출일 수 있음을 의미합니다. 게다가, 해당 기능은 따라오는 코드를 선점할 수 있는 것에서 당황할 수 있습니다. Zig에서, 무언가가 함수를 호출하면, 그것은 함수 호출처럼 보입니다; 그렇지 않으면, 그것은 함수 호출처럼 보이지 않습니다. 만약 그것이 오류를 시킬 수 있으면, 구문에서 명시적이며, 오류 처리는 오류 유형을 통해 처리되고 catch 또는 try로 처리될 수 ​​있습니다.

Zig의 목표는 Go, Rust, Carbon, 및 Nim과 같은 같은 기간에 설계된 다른 많은 언어의 목표와 대조적입니다. 일반적으로, 이들 언어는 연산자 오버로딩과 같은 추가 기능, 값 (속성)으로 가장한 함수, 및 대규모 프로그램 구축을 돕기 위한 많은 다른 기능으로 더 복잡합니다. 이들 종류의 특색은 C++의 접근 방식과 더 공통적이고, 이들 언어는 그 언어의 선을 더 잘 따라 다닙니다. Zig는 유형 시스템을 보다 보수적으로 확장하여, 컴파일 타임 제네릭을 지원하고 comptime 지시문으로 덕 타이핑 형태를 수용합니다.

Memory handling

C 프로그램에서 버그의 주요 원천 중 하나는 malloc을 기반으로 한 메모리 관리 시스템입니다. malloc은 코드에서 사용을 위한 메모리 블록을 따로 설정하고 해당 메모리에 대한 참조를 포인터로 반환합니다. 프로그램이 메모리를 더 이상 필요하지 않을 때 메모리가 릴리스 되도록 보증하는 시스템이 없으며, 이는 모든 사용가능한 메모리, 메모리 누출을 사용하는 프로그램으로 이어질 수 있습니다. 보다 공통적인 것은 적절하게 할당된 메모리 객체를 참조하지 않는 댕글링 포인터입니다.

이들 문제에 대한 공통적인 해결책은 쓰레기 수집기 (GC)로, 이전에 할당된 메모리로의 포인터에 대한 프로그램을 검사하고, 더 이상 그것들을 가리키는 어떤 것을 가지지 않는 임의의 블록을 제거합니다. 비록 이것이 메모리 오류를 크게 줄이거나 심지어 제거할지라도, GC 시스템은 수동 메모리 관리에 비해 상대적으로 느리고, 예측할 수 없는 성능을 가져서 시스템 프로그래밍에 적합하지 않게 만듭니다. 또 다른 해결책은 자동 참조 카운팅 (ARC)이며, 이는 폐기된 메모리의 블록을 식별하는 것과 같은 기본 개념을 구현하지만, 포인터 수를 블록으로 유지함으로써 포인터 생성과 파괴 시간에서 그렇게 하며, 모든 각 포인터 생성과 파괴 작업에 참조 카운터 조정 오버-헤드를 추가하는 비용으로 불필요하게 렌더링되는 철저한 포인터 검색을 수행할 필요가 없음을 의미합니다.

Zig는 C와 유사하거나 더 나은 성능을 제공하는 것을 목표로 하므로, GC 및 ARC는 적합한 해결책이 아닙니다. 대신, 그것은 2022년 기준 옵션 유형으로 알려진 개념을 사용합니다. 포인터가 아무것도 가리키지 않거나 nil을 가리키는 대신, 별도의 유형은 선택적으로 비어있는 데이터를 나타내기 위해 사용됩니다. 이것은 포인터를 갖는 구조와 포인터가 유효한지 여부를 나타내지 부울을 갖는 구조를 사용하는 것과 유사하지만, 부울의 상태는 언어에 의해 보이지 않게 관리되고 프로그래머에 의해 명시적으로 관리될 필요가 없습니다. 따라서, 예를 들어, 포인터가 선언될 때 그것은 "할당되지 않은"것으로 설정되고, 해당 포인터가 malloc에서 값을 받을 때, 그것은 malloc이 성공하면 "할당된" 것으로 설정됩니다.

이 모델의 장점은 매우 낮거나 영 오버헤드를 가진다는 것입니다; 컴파일러는 단순 포인터와 달리 포인터가 조작될 때 선택 유형을 따라 전달할 코드를 만들어야 하지만, 이것은 런타임 지원없이 컴파일 시간에 가능한 메모리 문제를 직접 표현하는 것을 허용합니다. 예를 들어, 널 값을 갖는 포인터를 생성하고 그런-다음 사용하려고 시도하는 것은 C에서 완벽하게 허용되며, 널-포인터 오류로 이어집니다. 대조적으로, 옵션 유형을 사용하는 언어는 모든 코드 경로가 포인터가 유효할 때만 사용하려고 시도하는지 확인할 수 있습니다. 이것이 모든 잠재적 문제를 제거하지는 않지만, 런타임에 오류가 발생하는 문제가 더 정확하게 위치하여 설명될 수 있습니다.

Zig에서 메모리 관리에 대해 또 다른 변경 사항은 libc에서 메모리 관리 함수를 호출하는 것과 대조적으로 실제 할당이 동작을 설명하는 struct를 통해 처리된다는 것입니다. 예를 들어, C에서 또 다른 문자열의 여러 사본을 포함하는 문자열을 만드는 함수를 작성하기를 원하면, 함수가 다음과 같을 것입니다:

const char* repeat(const char* original, size_t times);

코드에서, 함수는 original의 크기를 검사하고 그런-다음 malloc times의 길이를 검사하여 구축할 문자열에 대한 메모리를 따로 설정합니다. 해당 malloc은 그것을 호출하는 함수에 보이지 않으며, 만약 그것들이 나중에 메모리를 릴리스하지 못하면, 누출이 발생할 것입니다. Zig에서, 이것은 다음과 같은 함수를 사용하여 처리될 수 있습니다:

fn repeat(allocator: *std.mem.Allocator, original: []const u8, times: usize) std.mem.Allocator.Error![]const u8;

이 코드에서, allocator 변수는 할당을 수행해야 하는 코드를 설명하는 구조물이 전달되고, repeat 함수는 결과 문자열을 반환하거나, !, Allocator.Error에 의해 표시된대로 선택 유형을 사용합니다. 할당자를 입력으로 직접 표현함으로써, 메모리 할당은 또 다른 함수 내에서 "숨겨진" 적이 없으며, 메모리가 할당되도록 궁극적으로 호출하는 함수에 의해 항상 API에 노출됩니다. Zig의 표준 라이브러리에는 할당이 수행되지 않습니다. 게다가, struct가 무엇이든 가리킬 수 있으므로, 대안적인 allocator, 심지어 프로그램에 작성된 것조차 사용할 수 있습니다. 이것은, 예를 들어, 전체 메모리 페이지를 전형적으로 할당하는 운영 시스템 함수를 사용하지 않는 작은-객체 할당자를 허용할 수 있습니다.

선택 유형은 일반적인 기능을 제공하면서도 여전히 단순하고 일반적인 것인 언어 특색의 예입니다. 그것들은 널 포인터 문제를 해결하기 위해 사용되지 않아야 하며, 그것들은 "값 없음"이 적절한 답변인 임의의 유형의 값에도 유용합니다. 정수를 반환하는 함수 countTheNumberOfUsers와 결과를 보유하는 theCountedUsers를 생각해 보십시오. 많은 언어에서, 마법 단어가 countTheNumberOfUsers는 아직 호출되지 않았음을 나타 내기 위해 theCountedUsers에 배치될 것이지만, 많은 구현은 그것을 영으로 설정할 것입니다. Zig에서, 이것은 변수를 명확한 값을 "호출되지 않음"으로 설정하는 var theCountedUsers: ?i32 = null로 구현될 수 있습니다.

메모리 문제를 관리하는 데 도움이 되는 Zig의 또 다른 더 일반적인 특징은 defer의 개념으로, 이는 가능한 런타임 오류를 포함하여 어떤 일이 발생하든 함수의 끝에서 수행될 일부 코드를 표시합니다. 만약 특정 함수가 약간의 메모리를 할당하고 그런-다음 연산이 완료될 때 메모리를 처분하면, 어떤 일이 발생하더라도 그것이 해제되게 보장하기 위해 free를 연기하기 위해 줄을 추가할 수 있습니다.

Zig 메모리 관리는 숨겨진 할당을 피합니다. 할당은 언어에서 직접 관리되지 않습니다. 대신, 힙 접근은 분명하게 표준 라이브러리를 통해 수행됩니다.

Direct interaction with C

Zig는 새로운 Zig 코드와 기존 C 코드를 결합하는 이식성에 대한 점진적인 접근 방식을 촉진합니다. 이것을 하기 위해, 기존 C 라이브러리와 가능한 한 원활하게 상호 작용하는 것을 목표로 합니다. Zig는 전형적으로 이러한 방식으로 @import 지시문으로 자체 라이브러리를 가져옵니다:

const std = @import("std");

해당 파일 내의 Zig 코드는 이제 std 내에서 함수를 호출할 수 있습니다. 예를 들어:

std.debug.print("Hello, world!\n", .{});

C 코드로 작업하기 위해, 간단히 @import를 @cImport로 대체합니다:

const c = @cImport(@cInclude("soundio/soundio.h"));

Zig 코드는 이제 soundio 라이브러리에서 함수를 네이티브 Zig 코드인 것처럼 호출할 수 있습니다. Zig는 C의 일반적인 int 및 float와 달리 명시적으로 정의된 새로운 데이터 유형을 사용하므로, @intCast 및 @ptrCast를 포함한 C와 Zig 유형 사이의 데이터를 이동하는 데 적은 수의 지시문이 사용됩니다.

Comptime

comptime 키워드를 사용함으로써 프로그래머는 런타임과 대조적으로 컴파일 시간에서 코드의 섹션을 명시적으로 평가할 수 있습니다. 컴파일 시간에 코드를 실행할 수 있다는 것은 Zig에게 별도의 전처리기 언어에 대한 필요성 없이 매크로의 기능과 조건부 편의 기능을 가지도록 허용합니다.

컴파일 시간 동안, 유형은 첫 번째-클래스 시민이 됩니다. 이것은 컴파일-시간 오리 입력을 활성화하고, Zig가 일반 유형을 구현하는 방법입니다.

예를 들어, Zig에서, 일반 링크된 목록 유형은 다음과 같은 함수를 사용하여 구현될 수 있습니다:

fn LinkedList(comptime T: type) type;

이 함수는 일부 유형 T를 사용하고, 해당 데이터 유형을 갖는 링크된 목록을 정의하는 사용자 정의 struct를 반환합니다.

Compiler

Zig는 단지 새로운 언어인 것이 아닙니다: 그것은 역시 C 및 C ++ 컴파일러도 포함되고, 많은 다른 플랫폼에서 C 표준 라이브러리 (libc)와 C++ 표준 라이브러리 (libcxx)를 포함한 많은 헤더를 제공하는 명령 zig cc 와 zig c++를 활용함으로써 하나 또는 두 언어로 사용될 수 있습니다. 이것은 Zig의 cc와 c++ 하위-명령은 (Clang과 유사하게) 상자 밖에서 크로스 컴파일러 역할을 하도록 허용합니다.

Zig는 크로스-컴파일을 언어의 첫 번째-클래스 사용-사례로 취급합니다. 이것은 임의의 Zig 컴파일러가 대상 플랫폼 어떤 것에 대해 실행-가능한 바이너리를 컴파일할 수 있으며, 그 중 수십 개가 있음을 의미합니다. 여기에는 ARMx86-64와 같은 널리 사용되는 최신 시스템뿐만 아니라, PowerPC, SPARC, MIPS, RISC-V와 심지어 IBM z/Architectures (S390)도 포함됩니다. 툴-체인은 추가 소프트웨어 설치 없이 이들 대상 중 하나에 컴파일될 수 있으며, 필요한 모든 지원은 기본 시스템에 있습니다. 실험 지원은 AMD와 NVIDIA GPU 또는 PlayStation 4와 5와 같은 덜 알려진 플랫폼에 대해 제공됩니다 (다양한 수준의 지원).

다양한 운영 시스템 (주로 데스크탑)에는 크로스-컴파일이 가능합니다. 인기있는 유닉스-계열 운영 시스템과 Windows는 공식적으로 지원 (및 문서화)되지만, (최소) 응용 프로그램은 Android (Android NDK 포함) 또는 iOS를 위해 만들어져 왔습니다.

Zig는 LLLVM (C++로 작성)을 최적화를 위한 백엔드로 사용합니다. 버전 0.10이래로, Zig 컴파일러는 Zig 프로그래밍 언어로 작성됩니다. 즉, 그것은 자체-호스팅 컴파일러입니다. 자체-호스팅 링커는 자체-호스팅 컴파일러와 밀접하게 결합되어 있습니다.

Packages

버전 0.11.0은 실험 패키지 관리를 묶음으로 제공하지만, 공식 패키지 저장소는 사용할 수 없습니다. 대신, 패키지는 단순히 압축 파일을 가리키는 URL입니다. 각 패키지에는 표준 `build.zig` 파일 (Zig 컴파일러가 관례에 의해 소스 코드를 컴파일하기 위해 사용) 및 이상적으로는 패키지 이름과 버전을 갖는 메타데이터를 포함하는 `build.zig.zon` 파일이 포함됩니다.

Examples

Hello World

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {s}!\n", .{"world"});
}

Generic linked list

const std = @import("std");
const stdout = std.io.getStdOut().writer();

fn LinkedList(comptime T: type) type {
    return struct {
        const Self = @This();

        pub const Node = struct {
            next: ?*Node = null,
            data: T,
        };

        first: ?*Node = null,

        pub fn prepend(
            list: *Self,
            new_node: *Node,
        ) void {
            new_node.next = list.first;
            list.first = new_node;
        }

        pub fn format(
            list: Self,
            comptime fmt: []const u8,
            options: std.fmt.FormatOptions,
            out_stream: anytype,
        ) !void {
            try out_stream.writeAll("( ");

            var it = list.first;
            while (it) |node| : (it = node.next) {
                try std.fmt.formatType(
                    node.data,
                    fmt,
                    options,
                    out_stream,
                    1,
                );

                try out_stream.writeAll(" ");
            }

            try out_stream.writeAll(")");
        }
    };
}

pub fn main() !void {
    const ListU32 = LinkedList(u32);

    var list = ListU32{};
    var node1 = ListU32.Node{ .data = 1 };
    var node2 = ListU32.Node{ .data = 2 };
    var node3 = ListU32.Node{ .data = 3 };

    list.prepend(&node1);
    list.prepend(&node2);
    list.prepend(&node3);

    try stdout.print("{}\n", .{list});
    try stdout.print("{b}\n", .{list});
}
  • Output
    ( 3 2 1 ) 
    ( 11 10 1 )
    

String repetition with allocator

const std = @import("std");

fn repeat(
    allocator: *std.mem.Allocator,
    original: []const u8,
    times: usize,
) std.mem.Allocator.Error![]const u8 {
    var buffer = try allocator.alloc(
        u8,
        original.len * times,
    );

    for (0..times) |i| {
        std.mem.copyForwards(
            u8,
            buffer[(original.len * i)..],
            original,
        );
    }

    return buffer;
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var arena = std.heap.ArenaAllocator.init(
        std.heap.page_allocator,
    );
    defer arena.deinit();

    var allocator = arena.allocator();

    const original = "Hello ";
    const repeated = try repeat(
        &allocator,
        original,
        3,
    );

    try stdout.print("{s}\n", .{repeated});
}
  • Output
    Hello Hello Hello
    

History

"Zig"라는 이름은 문자 "Z"로 시작하여 무작위로 문자를 결합한 Python 스크립트와 함께 4 글자 단어를 생성하기 위해 모음 또는 "Y"를 포함하는 프로세스를 통해 선택된 것으로 알려졌습니다. 의도된 길이에도 불구하고, 3 글자 단어인 “Zig”는 궁극적으로 스크립트에 의해 생성된 다양한 조합에서 선택되었습니다.

LLVM을 백엔드로 사용하고, 많은 네이티브 대상을 지원하는, Zig 및 C++로 작성된 이전 부트-스트래핑 컴파일러는 버전 0.11로 제거되었습니다.

Projects