Visão Geral

Destaques

Linguagem simples e pequena

Concentre-se na depuração de sua aplicação em vez de depurar seus conhecimentos em uma linguagem de programação.

Toda a sintaxe do Zig é especificada com um Arquivo gramatical PEG de 500 linhas.

Não há nenhum fluxo de controle oculto, nenhuma alocação de memória oculta, nenhum pré-processador, e nenhuma macros. Se o código Zig não parece estar pulando para chamar uma função, então não está. Isto significa que você pode ter certeza de que o seguinte código chama apenas foo() e depois bar(), e isto é garantido sem a necessidade de saber os tipos de nada:

var a = b + c.d;
foo();
bar();

Exemplos de fluxo de controle oculto:

Zig promove a manutenção do código e a legibilidade, fazendo com que todo o fluxo de controle seja gerenciado exclusivamente com palavras-chave do idioma e chamadas de função.

Performance and Safety: Choose Two

Zig tem quatro modos de compilação, e todas elas podem ser misturadas e combinadas até a granularidade do escopo.

ParametroDebugReleaseSafeReleaseFastReleaseSmall
Otimizações - melhorar a velocidade, depuração de danos, tempo de compilação de danos-O3-O3-Os
Verificações de segurança em tempo de execução - velocidade do dano, tamanho do dano, acidente ao invés de comportamento indefinidoOnOn

Aqui temos Sobrecarga de Inteiros (Integer Overflow) que parece estar em tempo de compilação, independentemente do modo de compilação:

test.zig

test "integer overflow at compile time" {
    const x: u8 = 255;
    _ = x + 1;
}
$ zig test test.zig
./doctest-14ad65ca/test.zig:3:11: error: operation caused overflow
    _ = x + 1;
          ^

Aqui está o que parece em tempo de execução, em construções verificadas em termos de segurança:

test.zig

test "integer overflow at runtime" {
    var x: u8 = 255;
    x += 1;
}
$ zig test test.zig
Test [1/1] test "integer overflow at runtime"... thread 3336 panic: integer overflow
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-8fcba8d8/test.zig:3:7: 0x20683f in test "integer overflow at runtime" (test)
    x += 1;
      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/special/test_runner.zig:77:28: 0x22ccf2 in std.special.main (test)
        } else test_fn.func();
                           ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22522c in std.start.callMain (test)
            root.main();
                     ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:469:12: 0x2092de in std.start.callMainWithArgs (test)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:383:17: 0x208366 in std.start.posixCallMainAndExit (test)
    std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
                ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:296:5: 0x208172 in std.start._start (test)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
error: the following test command crashed:
doctest-8fcba8d8/zig-cache/o/9638dfc29dc05b30774601ed885b4c9f/test /home/runner/work/www.ziglang.org/www.ziglang.org/zig/zig

Esses rastreamentos de pilhas funcionam em todos os dispositivos, incluindo freestanding.

Com Zig pode-se confiar em um modo de compilação safe-enabled, e desativar seletivamente a segurança nos gargalos de desempenho. Por exemplo, o exemplo anterior poderia ser modificado desta forma:

test "actually undefined behavior" {
    @setRuntimeSafety(false);
    var x: u8 = 255;
    x += 1; // XXX undefined behavior!
}

Zig utiliza comportamento indefinido com inteligência tanto para a prevenção de bugs quanto para a melhoria do desempenho.

Por falar em desempenho, Zig é mais rápido que C.

Favor notar que Zig não é uma linguagem totalmente segura. Para aqueles interessados em seguir a história de segurança do Zig, inscrevam-se para estas questões:

Zig compete com C, em vez de depender dele

A biblioteca padrão do Zig se integra com a libc, mas não depende dela. Aqui está o “Hello World”:

hello.zig

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe hello.zig
$ ./hello
Hello, world!

Quando compilado com -O ReleaseSmall, símbolos de depuração são removidos (stripped), modo de thread única, isto produz um executável estático de 9,8 KiB para a plataforma x86_64-linux:

$ zig build-exe hello.zig --release-small --strip --single-threaded
$ wc -c hello
9944 hello
$ ldd hello
  not a dynamic executable

No Windows é ainda menor, gerando um binário de 4096 bytes:

$ zig build-exe hello.zig --release-small --strip --single-threaded -target x86_64-windows
$ wc -c hello.exe
4096 hello.exe
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows

Pedir declarações independentes de alto nível

Declarações de alto nível, tais como variáveis globais, são independentes de ordem e analisadas preguiçosamente. O valor de inicialização das variáveis globais é avaliado em tempo de compilação.

global_variables.zig

var y: i32 = add(10, x);
const x: i32 = add(12, 34);

test "global variables" {
    assert(x == 46);
    assert(y == 56);
}

fn add(a: i32, b: i32) i32 {
    return a + b;
}

const std = @import("std");
const assert = std.debug.assert;
$ zig test global_variables.zig
Test [1/1] test "global variables"... 
All 1 tests passed.

Tipo optional em vez de ponteiros nulos

Em outras linguagens de programação, referências nulas são a fonte de muitas exceções de tempo de execução, e até mesmo são acusadas de ser o pior erro da ciência da computação.

As indicações Unadorned Zig não podem ser nulas:

test "null @intToPtr" {
    _ = @intToPtr(*i32, 0x0);
}
$ zig test test.zig
./doctest-eb2f229b/test.zig:2:9: error: pointer type '*i32' does not allow address zero
    _ = @intToPtr(*i32, 0x0);
        ^

Entretanto, qualquer tipo pode ser transformado em um tipo optional prefixando-o com ?:

optional_syntax.zig

const std = @import("std");
const assert = std.debug.assert;

test "null @intToPtr" {
    const ptr = @intToPtr(?*i32, 0x0);
    assert(ptr == null);
}
$ zig test optional_syntax.zig
Test [1/1] test "null @intToPtr"... 
All 1 tests passed.

Para utilizar um valor opcional, pode-se utilizar orelse para fornecer um valor padrão:

// malloc prototype included for reference
extern fn malloc(size: size_t) ?*u8;

fn doAThing() ?*Foo {
    const ptr = malloc(1234) orelse return null;
    // ...
}

Outra opção é usar if:

fn doAThing(optional_foo: ?*Foo) void {
    // do some stuff

    if (optional_foo) |foo| {
        doSomethingWithFoo(foo);
    }

    // do some stuff
}

A mesma sintaxe funciona com while:

iterator.zig

const std = @import("std");

pub fn main() void {
    const msg = "hello this is dog";
    var it = std.mem.tokenize(u8, msg, " ");
    while (it.next()) |item| {
        std.debug.print("{s}\n", .{item});
    }
}
$ zig build-exe iterator.zig
$ ./iterator
hello
this
is
dog

Gerenciamento manual de memória

Uma biblioteca escrita em Zig é elegível para ser usada em qualquer lugar:

Para conseguir isto, os programadores Zig devem gerenciar sua própria memória e devem lidar com falhas na alocação de memória.

Isto também é verdade para a Biblioteca Padrão Zig. Qualquer função que precise alocar memória aceita um parâmetro do alocador. Como resultado, a Biblioteca Padrão Zig pode ser usada até mesmo para o dispositivo freestanding.

Além de Uma nova tomada de controle de erros, Zig fornece defer e errdefer para tornar a gestão de todos os recursos - não apenas a memória - simples e facilmente verificável.

Por exemplo o defer, veja Integração com bibliotecas C sem FFI/bindings. Aqui está um exemplo de utilização de errdefer:

const Device = struct {
    name: []u8,

    fn create(allocator: *Allocator, id: u32) !Device {
        const device = try allocator.create(Device);
        errdefer allocator.destroy(device);

        device.name = try std.fmt.allocPrint(allocator, "Device(id={d})", id);
        errdefer allocator.free(device.name);

        if (id == 0) return error.ReservedDeviceId;

        return device;
    }
};

Uma nova tomada de controle de erros

Os erros são valores, e não podem ser ignorados:

discard.zig

const std = @import("std");

pub fn main() void {
    _ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
}
$ zig build-exe discard.zig
./doctest-8708ba50/discard.zig:4:30: error: error is discarded. consider using `try`, `catch`, or `if`
    _ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
                             ^

Os erros podem ser tratados com catch:

catch.zig

const std = @import("std");

pub fn main() void {
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch |err| label: {
        std.debug.print("unable to open file: {e}\n", .{err});
        const stderr = std.io.getStdErr();
        break :label stderr;
    };
    file.writeAll("all your codebase are belong to us\n") catch return;
}
$ zig build-exe catch.zig
$ ./catch
unable to open file: error.FileNotFound
all your codebase are belong to us

A palavra-chave try é um atalho para catch |err| return err:

try.zig

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
    defer file.close();
    try file.writeAll("all your codebase are belong to us\n");
}
$ zig build-exe try.zig
$ ./try
error: FileNotFound
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/os.zig:1384:23: 0x224efd in std.os.openatZ (try)
            .NOENT => return error.FileNotFound,
                      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:999:13: 0x20c67b in std.fs.Dir.openFileZ (try)
            try os.openatZ(self.fd, sub_path, os_flags, 0);
            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:930:9: 0x20a511 in std.fs.Dir.openFile (try)
        return self.openFileZ(&path_c, flags);
        ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-294604f1/try.zig:4:18: 0x229a55 in main (try)
    const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
                 ^

Note que é um Rastreamento do retorno de erro, não um rastreamento da pilha. O código não pagou o preço de desenrolar a pilha para chegar a esse rastreamento.

O switch palavra-chave utilizada em um erro garante que todos os erros possíveis sejam tratados:

test.zig

const std = @import("std");

test "switch on error" {
    _ = parseInt("hi", 10) catch |err| switch (err) {};
}

fn parseInt(buf: []const u8, radix: u8) !u64 {
    var x: u64 = 0;

    for (buf) |c| {
        const digit = try charToDigit(c);

        if (digit >= radix) {
            return error.DigitExceedsRadix;
        }

        x = try std.math.mul(u64, x, radix);
        x = try std.math.add(u64, x, digit);
    }

    return x;
}

fn charToDigit(c: u8) !u8 {
    const value = switch (c) {
        '0'...'9' => c - '0',
        'A'...'Z' => c - 'A' + 10,
        'a'...'z' => c - 'a' + 10,
        else => return error.InvalidCharacter,
    };

    return value;
}
$ zig test test.zig
./doctest-e1899c0a/test.zig:4:40: error: error.Overflow not handled in switch
    _ = parseInt("hi", 10) catch |err| switch (err) {};
                                       ^
./doctest-e1899c0a/test.zig:3:24: note: referenced here
test "switch on error" {
                       ^
./doctest-e1899c0a/test.zig:4:40: error: error.DigitExceedsRadix not handled in switch
    _ = parseInt("hi", 10) catch |err| switch (err) {};
                                       ^
./doctest-e1899c0a/test.zig:4:40: error: error.InvalidCharacter not handled in switch
    _ = parseInt("hi", 10) catch |err| switch (err) {};
                                       ^

A palavra-chave unreachable é usado para afirmar que não ocorrerão erros:

unreachable.zig

const std = @import("std");

pub fn main() void {
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
    file.writeAll("all your codebase are belong to us\n") catch unreachable;
}
$ zig build-exe unreachable.zig
$ ./unreachable
thread 3427 panic: attempt to unwrap error: FileNotFound
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/os.zig:1384:23: 0x224d4d in std.os.openatZ (unreachable)
            .NOENT => return error.FileNotFound,
                      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:999:13: 0x20c5eb in std.fs.Dir.openFileZ (unreachable)
            try os.openatZ(self.fd, sub_path, os_flags, 0);
            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:930:9: 0x20a481 in std.fs.Dir.openFile (unreachable)
        return self.openFileZ(&path_c, flags);
        ^
???:?:?: 0x20bde2 in ??? (???)
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-adc9fa03/unreachable.zig:4:71: 0x2298e2 in main (unreachable)
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
                                                                      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22210c in std.start.callMain (unreachable)
            root.main();
                     ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:469:12: 0x20619e in std.start.callMainWithArgs (unreachable)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:383:17: 0x205226 in std.start.posixCallMainAndExit (unreachable)
    std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
                ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:296:5: 0x205032 in std.start._start (unreachable)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
(process terminated by signal)

Isto invoca um comportamento indefinido nos modos de compilação inseguros, portanto, certifique-se de usá-lo somente quando o sucesso for garantido.

Rastreamento de pilha em todos os dispositivos

O rastreamento da pilha e rastreamento do retorno de erros mostram que nesta página funcionam todos os dispositivos da tabela Suporte Tier 1 e alguns da Suporte Tier 2. Até mesmo freestanding!

Além disso, a biblioteca padrão tem a capacidade de capturar um traço de pilha em qualquer ponto e depois despejá-la em erro padrão mais tarde:

stack_traces.zig

const std = @import("std");
const builtin = @import("builtin");

var address_buffer: [8]usize = undefined;

var trace1 = std.builtin.StackTrace{
    .instruction_addresses = address_buffer[0..4],
    .index = 0,
};

var trace2 = std.builtin.StackTrace{
    .instruction_addresses = address_buffer[4..],
    .index = 0,
};

pub fn main() void {
    foo();
    bar();

    std.debug.print("first one:\n", .{});
    std.debug.dumpStackTrace(trace1);
    std.debug.print("\n\nsecond one:\n", .{});
    std.debug.dumpStackTrace(trace2);
}

fn foo() void {
    std.debug.captureStackTrace(null, &trace1);
}

fn bar() void {
    std.debug.captureStackTrace(null, &trace2);
}
$ zig build-exe stack_traces.zig
$ ./stack_traces
first one:
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/debug.zig:186:29: 0x2322cc in std.debug.captureStackTrace (stack_traces)
            addr.* = it.next() orelse {
                            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:27:32: 0x2308ac in foo (stack_traces)
    std.debug.captureStackTrace(null, &trace1);
                               ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:17:8: 0x229878 in main (stack_traces)
    foo();
       ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22212c in std.start.callMain (stack_traces)
            root.main();
                     ^


second one:
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/debug.zig:186:29: 0x2322cc in std.debug.captureStackTrace (stack_traces)
            addr.* = it.next() orelse {
                            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:31:32: 0x2308cc in bar (stack_traces)
    std.debug.captureStackTrace(null, &trace2);
                               ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:18:8: 0x22987d in main (stack_traces)
    bar();
       ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22212c in std.start.callMain (stack_traces)
            root.main();
                     ^

Você pode ver esta técnica sendo utilizada no projeto GeneralPurposeDebugAllocator.

Estruturas e funções genéricas de dados

Os tipos são valores que devem ser conhecidos em tempo de compilação:

types.zig

const std = @import("std");
const assert = std.debug.assert;

test "types are values" {
    const T1 = u8;
    const T2 = bool;
    assert(T1 != T2);

    const x: T2 = true;
    assert(x);
}
$ zig test types.zig
Test [1/1] test "types are values"... 
All 1 tests passed.

Uma estrutura de dados genérica é simplesmente uma função que retorna um type:

generics.zig

const std = @import("std");

fn List(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,
    };
}

pub fn main() void {
    var buffer: [10]i32 = undefined;
    var list = List(i32){
        .items = &buffer,
        .len = 0,
    };

    std.debug.print("{d}\n", .{list.items.len});
}
$ zig build-exe generics.zig
$ ./generics
10

Compilar o tempo de reflexão e compilar o tempo de execução do código

A função de compilação @typeInfo proporciona reflexão:

reflection.zig

const std = @import("std");

const Header = struct {
    magic: u32,
    name: []const u8,
};

pub fn main() void {
    printInfoAboutStruct(Header);
}

fn printInfoAboutStruct(comptime T: type) void {
    const info = @typeInfo(T);
    inline for (info.Struct.fields) |field| {
        std.debug.print(
            "{s} has a field called {s} with type {s}\n",
            .{
                @typeName(T),
                field.name,
                @typeName(field.field_type),
            },
        );
    }
}
$ zig build-exe reflection.zig
$ ./reflection
Header has a field called magic with type u32
Header has a field called name with type []const u8

A Biblioteca Padrão Zig usa esta técnica para implementar a impressão formatada. Apesar de ser uma Linguagem simples e pequena, A impressão formatada em Zig é implementada inteiramente em Zig. Enquanto isso, em C, os erros de compilação para impressão são codificados pelo compilador de forma arcaica. Da mesma forma, em Rust a macro de impressão formatada é codificada de forma arcaica e complexa pelo compilador.

Zig também pode avaliar funções e blocos de código em tempo de compilação. Em alguns contextos, tais como inicializações globais de variáveis, a expressão é implicitamente avaliada em tempo de compilação. Caso contrário, é possível avaliar explicitamente o código em tempo de compilação com a palavra-chave comptime. Isto pode ser especialmente poderoso quando combinado com afirmações:

test.zig

const std = @import("std");
const assert = std.debug.assert;

fn fibonacci(x: u32) u32 {
    if (x <= 1) return x;
    return fibonacci(x - 1) + fibonacci(x - 2);
}

test "compile-time evaluation" {
    var array: [fibonacci(6)]i32 = undefined;

    comptime {
        assert(array.len == 12345);
    }
}
$ zig test test.zig
./zig/lib/std/debug.zig:226:14: error: reached unreachable code
    if (!ok) unreachable; // assertion failure
             ^
./doctest-2dbfc381/test.zig:13:15: note: called from here
        assert(array.len == 12345);
              ^
./doctest-2dbfc381/test.zig:9:32: note: called from here
test "compile-time evaluation" {
                               ^
./doctest-2dbfc381/test.zig:13:15: note: referenced here
        assert(array.len == 12345);
              ^

Integration with C libraries without FFI/bindings

@cImport importa diretamente tipos, variáveis, funções e macros simples para uso em Zig. Ele até traduz funções em linha de C para Zig.

Aqui está um exemplo de emissão de uma onda sinusoidal usando libsoundio:

sine.zig

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

fn sio_err(err: c_int) !void {
    switch (@intToEnum(c.SoundIoError, err)) {
        .None => {},
        .NoMem => return error.NoMem,
        .InitAudioBackend => return error.InitAudioBackend,
        .SystemResources => return error.SystemResources,
        .OpeningDevice => return error.OpeningDevice,
        .NoSuchDevice => return error.NoSuchDevice,
        .Invalid => return error.Invalid,
        .BackendUnavailable => return error.BackendUnavailable,
        .Streaming => return error.Streaming,
        .IncompatibleDevice => return error.IncompatibleDevice,
        .NoSuchClient => return error.NoSuchClient,
        .IncompatibleBackend => return error.IncompatibleBackend,
        .BackendDisconnected => return error.BackendDisconnected,
        .Interrupted => return error.Interrupted,
        .Underflow => return error.Underflow,
        .EncodingString => return error.EncodingString,
        else => return error.Unknown,
    }
}

var seconds_offset: f32 = 0;

fn write_callback(
    maybe_outstream: ?[*]c.SoundIoOutStream,
    frame_count_min: c_int,
    frame_count_max: c_int,
) callconv(.C) void {
    const outstream = @ptrCast(*c.SoundIoOutStream, maybe_outstream);
    const layout = &outstream.layout;
    const float_sample_rate = outstream.sample_rate;
    const seconds_per_frame = 1.0 / @intToFloat(f32, float_sample_rate);
    var frames_left = frame_count_max;

    while (frames_left > 0) {
        var frame_count = frames_left;

        var areas: [*]c.SoundIoChannelArea = undefined;
        sio_err(c.soundio_outstream_begin_write(
            maybe_outstream,
            @ptrCast([*]?[*]c.SoundIoChannelArea, &areas),
            &frame_count,
        )) catch |err| std.debug.panic("write failed: {s}", .{@errorName(err)});

        if (frame_count == 0) break;

        const pitch = 440.0;
        const radians_per_second = pitch * 2.0 * std.math.pi;
        var frame: c_int = 0;
        while (frame < frame_count) : (frame += 1) {
            const sample = std.math.sin((seconds_offset + @intToFloat(f32, frame) *
                seconds_per_frame) * radians_per_second);
            {
                var channel: usize = 0;
                while (channel < @intCast(usize, layout.channel_count)) : (channel += 1) {
                    const channel_ptr = areas[channel].ptr;
                    const sample_ptr = &channel_ptr[@intCast(usize, areas[channel].step * frame)];
                    @ptrCast(*f32, @alignCast(@alignOf(f32), sample_ptr)).* = sample;
                }
            }
        }
        seconds_offset += seconds_per_frame * @intToFloat(f32, frame_count);

        sio_err(c.soundio_outstream_end_write(maybe_outstream)) catch |err| std.debug.panic("end write failed: {s}", .{@errorName(err)});

        frames_left -= frame_count;
    }
}

pub fn main() !void {
    const soundio = c.soundio_create();
    defer c.soundio_destroy(soundio);

    try sio_err(c.soundio_connect(soundio));

    c.soundio_flush_events(soundio);

    const default_output_index = c.soundio_default_output_device_index(soundio);
    if (default_output_index < 0) return error.NoOutputDeviceFound;

    const device = c.soundio_get_output_device(soundio, default_output_index) orelse return error.OutOfMemory;
    defer c.soundio_device_unref(device);

    std.debug.print("Output device: {s}\n", .{device.*.name});

    const outstream = c.soundio_outstream_create(device) orelse return error.OutOfMemory;
    defer c.soundio_outstream_destroy(outstream);

    outstream.*.format = @intToEnum(c.SoundIoFormat, c.SoundIoFormatFloat32NE);
    outstream.*.write_callback = write_callback;

    try sio_err(c.soundio_outstream_open(outstream));

    try sio_err(c.soundio_outstream_start(outstream));

    while (true) c.soundio_wait_events(soundio);
}

$ zig build-exe sine.zig -lsoundio -lc
$ ./sine
Output device: Built-in Audio Analog Stereo
^C

Este código Zig é significativamente mais simples do que o equivalente ao código C, bem como ter mais proteções de segurança, e tudo isso é conseguido através da importação direta do arquivo de cabeçalho C - sem ligações de API.

Zig é melhor no uso de bibliotecas C do que C é no uso de bibliotecas C.

O Zig também é um compilador C

Aqui está um exemplo de Zig compilando código C:

hello.c

#include <stdio.h>

int main(int argc, char **argv) {
    printf("Hello world\n");
    return 0;
}
$ zig build-exe --c-source hello.c --library c
$ ./hello
Hello world

Você pode utilizar --verbose-cc para ver quais os comandos que o compilador C executará:

$ zig build-exe --c-source hello.c --library c --verbose-cc
zig cc -MD -MV -MF zig-cache/tmp/42zL6fBH8fSo-hello.o.d -nostdinc -fno-spell-checking -isystem /home/andy/dev/zig/build/lib/zig/include -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-gnu -isystem /home/andy/dev/zig/build/lib/zig/libc/include/generic-glibc -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-any -isystem /home/andy/dev/zig/build/lib/zig/libc/include/any-linux-any -march=native -g -fstack-protector-strong --param ssp-buffer-size=4 -fno-omit-frame-pointer -o zig-cache/tmp/42zL6fBH8fSo-hello.o -c hello.c -fPIC

Note que se eu executar o comando novamente, não há saída, e ele termina instantaneamente:

$ time zig build-exe --c-source hello.c --library c --verbose-cc

real	0m0.027s
user	0m0.018s
sys	0m0.009s

Isto se deve a Cache de Compilação. Zig analisa automaticamente o arquivo .d usando um sistema de cache robusto para evitar duplicação de trabalho.

O Zig não só pode compilar o código C, mas há uma razão muito boa para usar Zig como um compilador C: Zig vincula-se com a libc.

Exportar funções, variáveis e tipos definidos pelo código C para depender de

Um dos casos de uso primário para Zig é a exportação de uma biblioteca com a ABI C para outras linguagens de programação a serem utilizadas. A palavra-chave export em frente às funções, variáveis e tipos faz com que elas façam parte da API da biblioteca:

mathtest.zig

export fn add(a: i32, b: i32) i32 {
    return a + b;
}

Fazer uma biblioteca estática:

$ zig build-lib mathtest.zig

Fazer uma biblioteca dinâmica:

$ zig build-lib mathtest.zig -dynamic

Eis um exemplo com o Sistema de Compilação do Zig:

test.c

#include "mathtest.h"
#include <stdio.h>

int main(int argc, char **argv) {
    int32_t result = add(42, 1337);
    printf("%d\n", result);
    return 0;
}

build.zig

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));

    const exe = b.addExecutable("test", null);
    exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
    exe.linkLibrary(lib);
    exe.linkSystemLibrary("c");

    b.default_step.dependOn(&exe.step);

    const run_cmd = exe.run();

    const test_step = b.step("test", "Test the program");
    test_step.dependOn(&run_cmd.step);
}

$ zig build test
1379

A compilação cruzada é um caso de uso de primeira classe

Zig pode compilar para qualquer um dos dispositivos listados na Tabela de Suporte com Suporte Tier 3 ou melhor. Nenhum “conjunto de ferramentas cruzada” precisa ser instalada ou algo parecido. Aqui está um “Hello World” nativo:

hello.zig

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe hello.zig
$ ./hello
Hello, world!

Você pode compilar para: x86_64-windows, x86_64-macosx, e aarch64v8-linux:

$ zig build-exe hello.zig -target x86_64-windows
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows
$ zig build-exe hello.zig -target x86_64-macosx
$ file hello
hello: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ zig build-exe hello.zig -target aarch64v8-linux
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped

Isto funciona em qualquer Tier 3+ dispositivos, para qualquer Tier 3+ plataformas.

Zig vinculado com libc

Você pode encontrar as plataformas disponíveis que suportam libc, através do comando zig targets:

...
 "libc": [
  "aarch64_be-linux-gnu",
  "aarch64_be-linux-musl",
  "aarch64_be-windows-gnu",
  "aarch64-linux-gnu",
  "aarch64-linux-musl",
  "aarch64-windows-gnu",
  "armeb-linux-gnueabi",
  "armeb-linux-gnueabihf",
  "armeb-linux-musleabi",
  "armeb-linux-musleabihf",
  "armeb-windows-gnu",
  "arm-linux-gnueabi",
  "arm-linux-gnueabihf",
  "arm-linux-musleabi",
  "arm-linux-musleabihf",
  "arm-windows-gnu",
  "i386-linux-gnu",
  "i386-linux-musl",
  "i386-windows-gnu",
  "mips64el-linux-gnuabi64",
  "mips64el-linux-gnuabin32",
  "mips64el-linux-musl",
  "mips64-linux-gnuabi64",
  "mips64-linux-gnuabin32",
  "mips64-linux-musl",
  "mipsel-linux-gnu",
  "mipsel-linux-musl",
  "mips-linux-gnu",
  "mips-linux-musl",
  "powerpc64le-linux-gnu",
  "powerpc64le-linux-musl",
  "powerpc64-linux-gnu",
  "powerpc64-linux-musl",
  "powerpc-linux-gnu",
  "powerpc-linux-musl",
  "riscv64-linux-gnu",
  "riscv64-linux-musl",
  "s390x-linux-gnu",
  "s390x-linux-musl",
  "sparc-linux-gnu",
  "sparcv9-linux-gnu",
  "wasm32-freestanding-musl",
  "x86_64-linux-gnu",
  "x86_64-linux-gnux32",
  "x86_64-linux-musl",
  "x86_64-windows-gnu"
 ],

O que isto significa é que --library c para estas plataformas não depende de nenhum arquivo do sistema!

Vejamos o exemplo do hello world em C novamente:

$ zig build-exe --c-source hello.c --library c
$ ./hello
Hello world
$ ldd ./hello
	linux-vdso.so.1 (0x00007ffd03dc9000)
	libc.so.6 => /lib/libc.so.6 (0x00007fc4b62be000)
	libm.so.6 => /lib/libm.so.6 (0x00007fc4b5f29000)
	libpthread.so.0 => /lib/libpthread.so.0 (0x00007fc4b5d0a000)
	libdl.so.2 => /lib/libdl.so.2 (0x00007fc4b5b06000)
	librt.so.1 => /lib/librt.so.1 (0x00007fc4b58fe000)
	/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc4b6672000)

glibc não suporta a compilação estática, mas musl suporta:

$ zig build-exe --c-source hello.c --library c -target x86_64-linux-musl
$ ./hello
Hello world
$ ldd hello
  not a dynamic executable

Neste exemplo, Zig construiu musl libc a partir da fonte e depois ligou-se a ela. A compilação da musl libc para x86_64-linux continua disponível graças ao sistema de caching, portanto, a qualquer momento esta libc é necessária novamente, ela estará disponível instantaneamente.

Isto significa que esta funcionalidade está disponível em qualquer plataforma. Os usuários de Windows e macOS podem criar códigos Zig e C, e vincular-se a libc, para qualquer uma das plataformas listados acima. Da mesma forma, o código pode ser compilado de forma cruzada para outras arquiteturas:

$ zig build-exe --c-source hello.c --library c -target aarch64v8-linux-gnu
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped

Em alguns aspectos, Zig é um compilador C melhor do que os próprios compiladores C!

Esta funcionalidade é mais do que o agrupamento de um conjunto de ferramentas de compilação cruzada junto ao Zig. Por exemplo, o tamanho total dos cabeçalhos da libc que o Zig envia é de 22 MiB sem compressão. Enquanto isso, os cabeçalhos para musl libc + linux em x86_64 são 8 MiB, e para glibc são 3,1 MiB (falta no glibc os cabeçalhos linux), ainda assim Zig atualmente envia com 40 libcs. Com um agrupamento ingênuo que seria de 444 MiB. No entanto, graças a isto, a ferramenta process_headers que eu fiz, e algum bom e velho trabalho manual, Os tarballs binários Zig permanecem em torno de 30 MiB no total, apesar de apoiar a libc para todos essas plataformas, bem como a compiler-rt, libunwind e libcxx, e apesar de ser um compilador C compatível com o clang. Para comparação, a compilação do binário no próprio Windows usando clang 8.0.0 do llvm.org é de 132 MiB.

Note que apenas no Suporte Tier 1 os dispositivos foram exaustivamente testados. Está previsto acrescentar mais libcs (inclusive para o Windows), e para adicionar cobertura de testes para compilação em relação a todas as libcs.

Já está sendo planejado para ter um Gerenciador de Pacotes do Zig, mas isso ainda não está pronto. Uma das coisas que será possível é criar um pacote para as bibliotecas C. Isto fará com que o Sistema de Compilação do Zig tornando mais atraente tanto para programadores Zig como para programadores C.

Sistema de Compilação do Zig

O Zig vem com um sistema de compilação, por isso você não precisa fazer, fabricar ou qualquer coisa do gênero.

$ zig init-exe
Created build.zig
Created src/main.zig

Next, try `zig build --help` or `zig build run`

src/main.zig

const std = @import("std");

pub fn main() anyerror!void {
    std.debug.print("All your base are belong to us.\n");
}

build.zig

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const mode = b.standardReleaseOptions();
    const exe = b.addExecutable("example", "src/main.zig");
    exe.setBuildMode(mode);

    const run_cmd = exe.run();

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    b.default_step.dependOn(&exe.step);
    b.installArtifact(exe);
}

Vamos dar uma olhada nesse menu --help.

$ zig build --help
Usage: zig build [steps] [options]

Steps:
  install (default)      Copy build artifacts to prefix path
  uninstall              Remove build artifacts from prefix path
  run                    Run the app

General Options:
  --help                 Print this help and exit
  --verbose              Print commands before executing them
  --prefix [path]        Override default install prefix
  --search-prefix [path] Add a path to look for binaries, libraries, headers

Project-Specific Options:
  -Dtarget=[string]      The CPU architecture, OS, and ABI to build for.
  -Drelease-safe=[bool]  optimizations on and safety on
  -Drelease-fast=[bool]  optimizations on and safety off
  -Drelease-small=[bool] size optimizations on and safety off

Advanced Options:
  --build-file [file]         Override path to build.zig
  --cache-dir [path]          Override path to zig cache directory
  --override-lib-dir [arg]    Override path to Zig lib directory
  --verbose-tokenize          Enable compiler debug output for tokenization
  --verbose-ast               Enable compiler debug output for parsing into an AST
  --verbose-link              Enable compiler debug output for linking
  --verbose-ir                Enable compiler debug output for Zig IR
  --verbose-llvm-ir           Enable compiler debug output for LLVM IR
  --verbose-cimport           Enable compiler debug output for C imports
  --verbose-cc                Enable compiler debug output for C compilation
  --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features

Você pode ver que uma das etapas disponíveis é executada.

$ zig build run
All your base are belong to us.

Aqui estão alguns exemplos de scripts de compilação:

Concorrência via funções Async

Zig v0.5.0 introduziu funções assíncronas. Esta característica não depende de um sistema operacional host ou mesmo de uma memória alocada em pilha. Isso significa que funções assimétricas estão disponíveis para os dispositivos freestanding.

Zig infere se uma função é assimétrica, e permite async/await em funções não assíncronas, o que significa que as bibliotecas Zig são agnósticos de bloqueio ao invés de E/S assíncrona. Zig evita as cores das funções (inglês).

A Biblioteca Padrão Zig implementa um loop de eventos que multiplexam as funções assimétricas em um pool de threads para concorrência M:N. A segurança multithreading e a detecção de corrida são áreas de pesquisa ativa.

Ampla gama de dispositivos suportados

Zig utiliza um sistema de “tiers” para comunicar o nível de suporte para diferentes dispositivos. Note que a barra para Suporte Tier 1 é maior - Suporte Tier 2 é ainda bastante útil.

Tabela de Suporte

free standingLinux 3.16+macOS 10.13+Windows 8.1+FreeBSD 12.0+NetBSD 8.0+DragonFly​BSD 5.8+UEFI
x86_64Tier 1Tier 1Tier 1Tier 2Tier 2Tier 2Tier 2Tier 2
arm64Tier 1Tier 2Tier 2Tier 3Tier 3Tier 3N/ATier 3
arm32Tier 1Tier 2N/ATier 3Tier 3Tier 3N/ATier 3
mips32 LETier 1Tier 2N/AN/ATier 3Tier 3N/AN/A
i386Tier 1Tier 2Tier 4Tier 2Tier 3Tier 3N/ATier 2
riscv64Tier 1Tier 2N/AN/ATier 3Tier 3N/ATier 3
bpfTier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
hexagonTier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
mips32 BETier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
mips64Tier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
amdgcnTier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
sparcTier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
s390xTier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
lanaiTier 3Tier 3N/AN/ATier 3Tier 3N/AN/A
powerpc32Tier 3Tier 3Tier 4N/ATier 3Tier 3N/AN/A
powerpc64Tier 3Tier 3Tier 4N/ATier 3Tier 3N/AN/A
avrTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
riscv32Tier 4Tier 4N/AN/ATier 4Tier 4N/ATier 4
xcoreTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
nvptxTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
msp430Tier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
r600Tier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
arcTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
tceTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
leTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
amdilTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
hsailTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
spirTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
kalimbaTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
shaveTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A
renderscriptTier 4Tier 4N/AN/ATier 4Tier 4N/AN/A

Tabela de suporte para WebAssembly

free standingemscriptenWASI
wasm32Tier 1Tier 3Tier 1
wasm64Tier 4Tier 4Tier 4

Tier System

Suporte Tier 1

Suporte Tier 2

Suporte Tier 3

Suporte Tier 4

Colaboradores de pacotes

O compilador Zig ainda não é completamente auto-hospedado, mas não importa o, permanecerá exatamente 3 etapas para deixar de usar um compilador C++ no sistema para ter um compilador Zig totalmente auto-hospedado para qualquer plataforma. Como observa Maya Rashish, portando o Zig para outras plataformas é divertido e rápido.

Os modos de compilação sem depuração (non-debug) são reprodutíveis/determináveis.

Há um Versão JSON da página de download.

Vários membros da equipe Zig têm experiência na manutenção de pacotes.