Zig is a general-purpose system programming language that emphasizes performance, simplicity and metaprogramming. The compiler is free and open-source software, released under an MIT License on Codeberg.

Zig was designed to replace the C programming language while maintaining C ABI compatibility. Unlike C it uses compile time code execution for metaprogramming instead of a preprocessor. At compile time types are first class and can be passed to functions, this system is used for generics. A core goal of the language is to have no hidden memory allocations or control flow. Low-level programming features include packed structs, arbitrary-width integers and multiple pointer types.

Zig was designed by Andrew Kelley and first announced in 2016. Development is funded by the Zig Software Foundation (ZSF) which receives corporate sponsorships as well as personal donations.

History The name Zig was picked using a script that generated random combinations of letters starting with the letter z.

The previous bootstrapping compiler, written in Zig and C++ using LLVM as a back-end, supporting many of its native targets, was removed in version 0.11. Newer versions of Zig use a prebuilt WebAssembly version of Zig to bootstrap itself.

On 26 November 2025, Zig development migrated from GitHub to Codeberg, citing GitHub's declining reliability under Microsoft ownership, particularly due to poor use of Generative AI on the platform, as a primary reason for this change. One cited example of the platform's declining reliability was the apparent unreliability and delays towards the fixing of bugs in GitHub's CI platform, GitHub Actions. In addition, the Zig development team had concerns with the Generative AI direction of GitHub, which was in contrast to the Zig project's policy against Generative AI. In 2026, Codeberg would introduce a policy against Generative AI, similar to the policy of the Zig project.

Syntax and features

Hello World program Below is a "Hello, World!" program in Zig. const std = @import("std");

pub fn main() void { std.debug.print("Hello, World!\n", .{}); }

Goals The primary goal of Zig is to be a better solution to the sorts of tasks that are currently solved with C. A primary concern in that respect is readability; Zig attempts to use existing concepts and syntax wherever possible, avoiding the addition of different syntax for similar concepts. Further, its goal is to be a language designed for "robustness, optimality and maintainability". The small and simple syntax is an important part of that maintainability, as one of the language's goals is to allow maintainers to debug code written in Zig without having to learn the intricacies of a language they might not be familiar with. Even with these changes, Zig can compile into and against existing C code; C headers can be included in a Zig project and their functions called, and Zig code can be linked into C projects by including the compiler-built headers.

Error handling is handled through error types and can be handled with or . Generics are achieved through compile time code generation and accommodating a form of duck typing with the directive.

Memory handling Memory allocation in the Zig standard library follows the convention that allocation is handled through s describing the action, as opposed to calling the memory management functions in libc. For instance, in C if one wants to write a function that makes a string containing multiple copies of another string, the function might look like this: const char* repeat(const char* original, size_t times);

In the code, the function would examine the size of and then malloc that length to set aside memory for the string it will build. That malloc is invisible to the functions calling it; if they fail to later release the memory, a leak will occur. In Zig, this might be handled using a function like: const Allocator = std.mem.Allocator;

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

In this code, the variable is passed a struct that describes what code should perform the allocation, and the function returns either the resulting string or, using the error union type as indicated by the , an Allocator.Error. By directly expressing the allocator as an input, memory allocation is never "hidden" within another function, it is always exposed to the API by the function that is ultimately calling for the memory to be allocated. No allocations are performed inside Zig’s standard library. Further, as the struct can point to anything, one can use alternative allocators, even ones written in the program. This can allow, for instance, small-object allocators that do not use the operating system functions that normally allocate an entire memory page.

The optional type is also an example of a language feature that offers general functionality while still being simple and generic. They do not have to be used to solve null pointer problems; they are also useful for any type of value where "no value" is an appropriate answer. Consider a function that returns an integer, and an integer variable, that holds the result. In many languages, a magic number would be placed in to indicate that has not yet been called, while many implementations would just set it to zero. In Zig, this could be implemented as an which sets the variable to a clear "not been called" value.

Another more general feature of Zig that also helps manage memory problems is the concept of , which marks some code to be performed at the end of a scope no matter what happens, including possible runtime errors. If a particular function allocates some memory and then disposes of it when the operation is complete, one can add a line to defer a to ensure it is released no matter what happens.

The Zig standard library avoids hidden allocations. Allocation is not managed in the language directly. Instead, heap access is done via the standard library, explicitly.

Direct interaction with C

Zig promotes a gradual approach to portability by providing direct interoperability of Zig code with C code, allowing code from one language to call code from the other. Since 0.16.0, this is implemented through Zig's build system, by enabling C libraries to be defined within the file of the Zig project, to enable for the Zig code to import the C libraries through the use of the keyword.

For inclusion of C code in a primarily Zig project, this is typically implemented in the file, in a similar fashion to the following example (note that this example is only of the relevant code):

const translate_c = b.addTranslateC(.{ .root_source_file = b.path("src/cheaders.h"), .target = target, .optimize = optimize, });

const exe = b.addExecutable(.{ .name = "example", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .optimize = optimize, .target = target, .imports = &.{ .{ .name = "c", .module = translate_c.createModule(), }, }, }), });

The C headers can then be called in Zig code with the following code:

const c = @import("c");

c.exampleFunction();

This is in line with how Zig imports its own libraries, similarly with the directive, typically in this fashion:

const std = @import("std");

For the alternate method of utilising and calling Zig code inside a primarily C program, this is more difficult than the aforementioned, as the Zig build system focusses on a Zig-based environment. However, it is a similar setup, as the Zig build system is still utilised for such a purpose in most scenarios. As C code naturally uses C header files () and macros, all Zig declarations, such as functions, would need to be expressed either within header files or declarations, as would be done for C interoperability with programming languages other than Zig. The following code is an example for a C project which uses Zig code, where the shared declarations between C and Zig code are contained within an file:

const sharedheader = b.addTranslateC(.{ .root_source_file = b.path("./src/extern.h"), .target = target, .optimize = optimize, .link_libc = true, });

const zig_code = b.addLibrary(.{ .name = "zigcodelib", .linkage = .static, .root_module = b.createModule(.{ .root_source_file = b.path("src/zigcode.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{ .{ .name = "flags.h", .module = sharedheader.createModule() }, }, }), });

const main_c_module = b.addModule("main", .{ .target = target, .optimize = mode, .link_libc = true, });

main_c_module.addCSourceFile(.{ .file = b.path("src/main.c"), .flags = &.{ "-Wall", "-pedantic" }, });

const exe = b.addExecutable(.{ .name = "example", .root_module = main_c_module, });

exe.root_module.linkLibrary(zig_code);

b.installArtifact(zig_code); b.installArtifact(exe);

Zig 0.16.0 deprecated , going forward C Translation will be handled via the Build System instead of using the language builtin, which has been 'deleted' from the language. Additionally the release deprecated std.Build.Step.TranslateC in favour using an explicit package dependency of the official translate-c package, which is the same implementation as the previous build step version with additional configuration options.

Prior to 0.16.0, the system used for C interoperability did not use the Zig build system, and instead used a directive only used for calling C code. This directive would often wrap an additional directive, which mirrored the macro that would be seen within C code. In these prior versions of Zig, C libraries are imported in the following manner: const c = @cImport(@cInclude("soundio/soundio.h")); This Zig code would be functionally equivalent to the following C code with the main difference being that the library is accessed through calls to the constant identifier , as seen in the current 0.16.0 example. #include <soundio/soundio.h>

The Zig code would then be able to call functions from soundio library in the same manner as calling functions from other Zig code, As Zig uses new data types that are explicitly defined, unlike C’s more generic and , a small number of directives are used to move data between the C and Zig types, including and . This is notably different from the current implementations, as Zig code would directly be able to import code from system header files, whereas the current implementation only allows for Zig code to import C code from local libraries defined within the users code.

Compile time evaluation Zig can evaluate sections of code at compile time instead of runtime using the keyword. Running code at compile time provides functionality similar to macros and conditional compilation, without the use of a separate preprocessor language.

Types become first-class citizens at compile time, allowing compile-time duck typing, which Zig uses to implement generic types. The example below declares a generic linked list: fn LinkedList(comptime T: type) type; The function takes the type and produces a concrete linked-list type based on .

Input/output as an interface Starting with version 0.16.0, all standard library input/output functionality in Zig requires passing an explicit instance, rather than relying on globally available I/O functions. The change addresses the "what color is your function" problem common to async programming, in which functions written to support asynchronous execution are typically incompatible with synchronous callers and vice versa. Zig's approach makes an implementation an explicit parameter rather than an implicit property of a function, allowing the same code to run under different execution strategies—including a thread-based implementation () and an experimental, work-in-progress stackful coroutine implementation ()—without requiring separate sets of functions for synchronous and asynchronous use.