is a general-purpose programming language and toolchain for maintaining , , and software.

Often the most efficient way to learn something new is to see examples, so this documentation shows how to use each of Zig's features. It is all on one page so you can search with your browser's search tool.

The code samples in this document are compiled and tested as part of the main test suite of Zig.

This HTML document depends on no external files, so you can use it offline.

The has its own documentation.

Zig's Standard Library contains commonly used algorithms, data structures, and definitions to help you build programs or libraries. You will see many examples of Zig's Standard Library used in this documentation. To learn more about the Zig Standard Library, visit the link above.

Alternatively, the Zig Standard Library documentation is provided with each Zig distribution. It can be rendered via a local webserver with:

Most of the time, it is more appropriate to write to stderr rather than stdout, and whether or not the message is successfully written to the stream is irrelevant. Also, formatted printing often comes in handy. For this common case, there is a simpler API:

In this case, the may be omitted from the return type of because no errors are returned from the function.

See also:

There are three types of comments. Normal comments are ignored, while and are used by the compiler to generate the package documentation.

There are no multiline comments. Zig has the property that each line of code can be tokenized independently.

A doc comment is one that begins with exactly three slashes (i.e. but not ); multiple doc comments in a row are merged together to form a multiline doc comment. The doc comment documents whatever immediately follows it.

Doc comments are only allowed in certain places; it is a compile error to have a doc comment in an unexpected place, such as in the middle of an expression, or just before a non-doc comment.

Doc comments can be interleaved with normal comments, which are ignored.

A top-level doc comment is one that begins with two slashes and an exclamation point: ; it documents the type which owns the containing .

It is a compile error if a top-level doc comment is not placed at the start of a namespace, before any expressions.

A namespace in Zig is created by , , , and .

They contain , declarations, and blocks.

Although namespaces use curly braces to surround their definition, they should not be confused with or function bodies.

, with the keyword and curly braces omitted.

Identifiers must start with an alphabetic character or underscore and may be followed by any number of alphanumeric characters or underscores. They must not overlap with any keywords. See .

If a name that does not fit these requirements is needed, such as for linking with external libraries, the syntax may be used.

In addition to the integer types above, arbitrary bit-width integers can be referenced by using an identifier of or followed by digits. For example, the identifier refers to a signed 7-bit integer. The maximum allowed bit-width of an integer type is .

See also:

See also:

String literals are constant single-item to null-terminated byte arrays. The type of string literals encodes both the length, and the fact that they are null-terminated, and thus they can be to both and . Dereferencing string literals converts them to .

Because Zig source code is , any non-ASCII bytes appearing within a string literal in source code carry their UTF-8 meaning into the content of the string in the Zig program; the bytes are not modified by the compiler. It is possible to embed non-UTF-8 bytes into a string literal using notation.

Indexing into a string containing non-ASCII bytes returns individual bytes, whether valid UTF-8 or not.

Unicode code point literals have type , the same as . All are valid in both string literals and Unicode code point literals.

See also:

Note that the maximum valid Unicode scalar value is .

Multiline string literals have no escapes and can span across multiple lines. To start a multiline string literal, use the token. Just like a comment, the string literal goes until the end of the line. The end of the line is not included in the string literal. However, if the next line begins with then a newline is appended and the string literal continues.

See also:

Use the keyword to assign a value to an identifier:

applies to all of the bytes that the identifier immediately addresses. have their own const-ness.

If you need a variable that you can modify, use the keyword:

Variables must be initialized:

Use to leave variables uninitialized:

can be to any type. Once this happens, it is no longer possible to detect that the value is . means the value could be anything, even something that is nonsense according to the type. Translated into English, means "Not a meaningful value. Using this value would be a bug. The value will be unused, or overwritten before being used."

In and mode, Zig writes bytes to undefined memory. This is to catch bugs early, and to help detect use of undefined memory in a debugger. However, this behavior is only an implementation feature, not a language semantic, so it is not guaranteed to be observable to code.

A destructuring assignment can separate elements of indexable aggregate types (, , ):

A destructuring expression may only appear within a block (i.e. not at scope). The left hand side of the assignment must consist of a comma separated list, each element of which may be either an lvalue (for instance, an existing ) or a variable declaration:

A destructure may be prefixed with the keyword, in which case the entire destructure expression is evaluated at . All s declared would be s and all expressions (both result locations and the assignee expression) are evaluated at .

See also:

Code written within one or more declarations can be used to ensure behavior meets expectations:

The code sample tests the to ensure that it returns given the input . From this test's perspective, the function is said to be .

is a tool that creates and runs a test build. By default, it builds and runs an executable program using the provided by the as its main entry point. During the build, declarations found while the given Zig source file are included for the default test runner to run and report on.

The shell output shown above displays two lines after the command. These lines are printed to standard error by the default test runner:

Test declarations contain the , followed by an optional name written as a or an , followed by a containing any valid Zig code that is allowed in a .

Non-named test blocks always run during test builds and are exempt from .

Test declarations are similar to : they have a return type and a block of code. The implicit return type of is the , and it cannot be changed. When a Zig source file is not built using the tool, the test declarations are omitted from the build.

Test declarations can be written in the same file, where code under test is written, or in a separate Zig source file. Since test declarations are top-level declarations, they are order-independent and can be written before or after the code under test.

See also:

Test declarations named using an identifier are . The identifier must refer to another declaration in scope. A doctest, like a , serves as documentation for the associated declaration, and will appear in the generated documentation for the declaration.

An effective doctest should be self-contained and focused on the declaration being tested, answering questions a new user might have about its interface or intended usage, while avoiding unnecessary or confusing details. A doctest is not a substitute for a doc comment, but rather a supplement and companion providing a testable, code-driven example, verified by .

The default test runner checks for an returned from a test. When a test returns an error, the test is considered a failure and its is output to standard error. The total number of failures will be reported after all tests have run.

One way to skip tests is to filter them out by using the command line parameter . This makes the test build only include tests whose name contains the supplied filter text. Note that non-named tests are run even when using the command line parameter.

To programmatically skip a test, make a return the error and the default test runner will consider the test as being skipped. The total number of skipped tests will be reported after all tests have run.

When code allocates using the 's testing allocator, , the default test runner will report any leaks that are found from using the testing allocator:

See also:

Use the to detect a test build:

The default test runner and the Zig Standard Library's testing namespace output messages to standard error.

The Zig Standard Library's namespace contains useful functions to help you create tests. In addition to the function, this document uses a couple of more functions as exemplified here:

The Zig Standard Library also contains functions to compare , strings, and more. See the rest of the namespace in the for more available functions.

has a few command line parameters which affect the compilation. See for a full list.

A variable is a unit of storage.

It is generally preferable to use rather than when declaring a variable. This causes less work for both humans and computers to do when reading code, and creates more optimization opportunities.

Variables are never allowed to shadow from an outer scope.

The keyword or builtin function can be used to link against a variable that is exported from another object. The keyword or builtin function can be used to make a variable available to other objects at link time. In both cases, the type of the variable must be C ABI compatible.

See also:

level variables have global lifetime and are order-independent and lazily analyzed. The initialization value of namespace level variables is implicitly . If a namespace level variable is then its value is -known, otherwise it is runtime-known.

Namespace level variables may be declared inside a , , , or :

It is also possible to have local variables with global lifetime by using inside functions.

A variable may be specified to be a thread-local variable using the keyword, which makes each thread work with a separate instance of the variable:

For , all thread local variables are treated as regular .

Thread local variables may not be .

Local variables occur inside , blocks, and labeled .

When a local variable is , it means that after initialization, the variable's value will not change. If the initialization value of a variable is -known, then the variable is also -known.

A local variable may be qualified with the keyword. This causes the variable's value to be -known, and all loads and stores of the variable to happen during semantic analysis of the program, rather than at runtime. All variables declared in a expression are implicitly variables.

Integer literals have no size limitation, and if any Illegal Behavior occurs, the compiler catches it.

However, once an integer value is no longer known at compile-time, it must have a known size, and is vulnerable to safety-checked .

In this function, values and are known only at runtime, and thus this division operation is vulnerable to both and .

Operators such as and cause on integer overflow. Alternative operators are provided for wrapping and saturating arithmetic on all targets. and perform wrapping arithmetic while and perform saturating arithmetic.

Zig supports arbitrary bit-width integers, referenced by using an identifier of or followed by digits. For example, the identifier refers to a signed 7-bit integer. The maximum allowed bit-width of an integer type is . For signed integer types, Zig uses a representation.

See also:

Zig has the following floating point types:

Float literals have type which is guaranteed to have the same precision and operations of the largest other floating point type, which is .

Float literals to any floating point type, and to any type when there is no fractional component.

There is no syntax for NaN, infinity, or negative infinity. For these special values, one must use the standard library:

By default floating point operations use mode, but you can switch to mode on a per-block basis:

For this test we have to separate code into two object files - otherwise the optimizer figures out all the values at compile-time, which operates in strict mode.

See also:

There is no operator overloading. When you see an operator in Zig, you know that it is doing something from this table, and nothing else.

Precedence ยง

Zig features partial operator precedence: not every operator has a defined precedence with every other operator. Mixing operators with ambiguous precedence is prohibited and parentheses are mandatory. Consequently, valid code is typically auto-descriptive; when reading and writing Zig code, there are very few precedence rules you'll need to remember.

operator_precedence.zig
const std = @import("std");

test "operator precedence" {
    // Arithmetic operators follow the conventional order of operations.
    {
        const a = 1 + 2 * 3 - 4 / 2;
        const b = 1 + (2 * 3) - (4 / 2);
        try std.testing.expectEqual(a, b);
    }

    // Remainder division is a special case. It still has a higher precedence than
    // addition and subtraction, but it has ambiguous precedence with itself and
    // other multiplication/division operators.
    {
        const a = 1 + 2 % 3;
        const b = 1 + (2 % 3);
        try std.testing.expectEqual(a, b);

        // `1 % 2 % 3`, `1 % 2 / 3`, etc. would be a compile error,
        // and parentheses would be required to disambiguate.
    }

    // Bitwise operators have ambiguous precedence with other bitwise operators.
    // However, non-shifting bitwise operators (&, |, ^) can be chained with themselves.
    {
        const a = 1 ^ 2 ^ 3;
        const b = (1 ^ 2) ^ 3;
        try std.testing.expectEqual(a, b);

        // `1 ^ 2 | 3`, `1 << 2 << 3`, etc. would be a compile error,
        // and parentheses would be required to disambiguate.

        // To mix bitwise operators, parentheses must be used.
        try std.testing.expectEqual(0x10101, (1 << 16) | (1 << 8) | 1);
    }

    // Comparison operators have lower precedence than both arithmetic and bitwise
    // operators. Comparisons cannot be chained.
    {
        const a = 1 | 4 <= 1 + 4;
        const b = (1 | 4) <= (1 + 4);
        try std.testing.expectEqual(a, b);

        // `1 == 2 >= 3`, etc. would be a compile error,
        // and parentheses would be required to disambiguate.
    }

    // Logical operators have lower precedence than comparison operators,
    // and may only be chained homogeneously (`and` with `and`, but not
    // with `or`, and vice versa).
    {
        const false_expr: bool = 7 < 2;
        const true_expr: bool = 1 + 1 == 2;

        const a = false_expr or false_expr or true_expr;
        const b = 7 < 2 or 7 < 2 or 1 + 1 == 2;
        const c = ((7 < 2) or (7 < 2)) or (1 + 1 == 2);
        try std.testing.expect(a and b and c);

        // `a or b and c`, etc. would be a compile error,
        // and parentheses would be required to disambiguate.
    }

    // `orelse` and `catch` may be chained with themselves, but not each other.
    {
        const some_null: ?u8 = null;

        const a = some_null orelse some_null orelse 2;
        const b = (some_null orelse some_null) orelse 2;
        try std.testing.expectEqual(a, b);

        // `a catch b orelse c`, etc. would be a compile error,
        // and parentheses would be required to disambiguate.

        // Prefix operators like `try` have higher precedence than `orelse`/`catch`.
        const c = try maybe() orelse 1;
        const d = (try maybe()) orelse 1;
        try std.testing.expectEqual(c, d);
    }
}

fn maybe() error{Fail}!?u8 {
    return null;
}
Shell
$ zig test operator_precedence.zig
1/1 operator_precedence.test.operator precedence...OK
All 1 tests passed.
ambiguous_precedence_orelse.zig
test "ambiguous precedence" {
    const x: ?bool = true;
    const y: bool = false;
    const z: bool = false;

    // The following expression is always considered ambiguous,
    // and, in this particular case, `(x orelse y) == z` would
    // produce a different result than `x orelse (y == z)`.
    _ = x orelse y == z;
}
Shell
$ zig build-obj ambiguous_precedence_orelse.zig
/home/ryan/Programming/zig/zig/doc/langref/ambiguous_precedence_orelse.zig:9:20: error: ambiguous operator precedence with operators ... orelse ... == ...; use parentheses to disambiguate
    _ = x orelse y == z;
                   ^~
/home/ryan/Programming/zig/zig/doc/langref/ambiguous_precedence_orelse.zig:9:11: note: previous ambiguous operator here
    _ = x orelse y == z;
          ^~~~~~
/home/ryan/Programming/zig/zig/doc/langref/ambiguous_precedence_orelse.zig:9:20: note: in zig 0.17.x, this was evaluated as (... orelse ...) == ...

There is a set of operators (prefix operators, suffix operators, etc) that have unambiguously higher precedence than all other operators. That set of operators has the following precedence table:

x() x[] x.y x.* x.?
a!b
x{}
!x -x -%x ~x &x ?x try
// lower precedence operators omitted

For the other operators, simplified operator precedence tables are provided below, each containing only the information that you'll need to know when reading/writing code that uses that particular category of operators. The operators that have ambiguous precedence or semantic errors with the relevant operators have been omitted, as have the operators with higher precedence (those shown above).

For arithmetic operators:

* / *% *| %
+ - +% -% +| -|
== != < > <= >=
and or

For bitwise operators:

<< <<| >> & ^ |
== != < > <= >=
and or

For null and error recovery operators:

catch orelse

For error set operators:

||

For array concatenation operators:

++

The full precedence rules can be found in the Grammar, which also includes an Operator Precedence Flowchart.