Zpět na domů

Zig vs C: CLI nástroj na 1000 řádků bez chyb

Článek rozebírá migraci CLI analyzátoru logů logz z C (3147 řádků) na Zig (1089 řádků). Srovnání kódu parsování argumentů/logů, defer, benchmarky. Výhody pro systémové nástroje: rychlost, bezpečnost paměti, kompaktnost.

Zig nahrazuje 3000 řádků C: reálný případ logz
Advertisement 728x90

Zig pro CLI nástroje: migrace z 3000 řádků C na 1000 řádků bez pádů

Vývojáři nástrojů pro příkazový řádek často narážejí na problémy jazyka C: ruční správa paměti, pády programu a objemný kód. Přepsání analyzátoru logů logz do Zigu zredukovalo objem z 3147 řádků na 1089, zrychlilo běh o 33 % a odstranilo pády. Rozbor migrace, ukázky kódu a benchmarky.

Problémy C v reálném projektu

Analyzátor logů logz parsuje access.log a error.log nginx/apache, filtruje podle úrovně (DEBUG, INFO, WARN, ERROR), data, IP, sestavuje žebříček chyb a sloupcové grafy v terminálu pomocí unicode.

V C kód narostl na 3147 řádků:

Google AdInline article slot
  • Funkce process_file — 400 řádků monolitického kódu.
  • Pády z přetečení bufferů (256 bajtů proti 300bajtovým řetězcům).
  • Úniky paměti, ladění přes Valgrind.
  • Parser data s chybami typu off-by-one.

Zpracování chyb přes -1/NULL/errno rozmazává kontext. Makefile se zapomínal, sestavování zpomalovalo bez -MMD.

Jednoduchý parser řetězce 2026-01-17 ERROR [nginx] 192.168.1.100 500 vyžadoval 50 řádků: strtok, NULL-kontroly, strncpy, uvolňování bufferů.

Výhody Zigu oproti C a Rustu

Zig zachovává kontrolu C, ale přidává nástroje:

Google AdInline article slot
  • defer file.close() — zaručené uvolnění bez skrytého toku řízení.
  • !T v signatuře — sjednocení chyb, kompilátor vyžaduje zpracování.
  • Křížová kompilace: zig build -Dtarget=x86_64-windows bez toolchainů.
  • Rychlé sestavení malých projektů.

Rust s borrow checkerem je nadbytečný pro CLI, které čte soubor do stdout.

Architektura logz — pipeline z 5 modulů:

  • Parsování argumentů.
  • Čtení souboru.
  • Parsování řádků.
  • Filtrování.
  • Statistika a výstup.

V C se vše míchalo v process_file. V Zig data proudí striktně dolů.

Google AdInline article slot

Srovnání kódu: argumenty CLI

C-verze (~80 řádků):

int parse_args(int argc, char **argv, Config *cfg) {
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--file") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "error: --file requires a value\n");
                return -1;
            }
            cfg->filename = argv[++i];
        } // podobně pro --level, --top-ip
    }
    return 0;
}

Zig (~40 řádků):

const Config = struct {
    filename: []const u8 = "",
    level: ?LogLevel = null,
    top_ip: u32 = 10,
    from_date: ?i64 = null,
};

fn parseArgs(allocator: std.mem.Allocator) !Config {
    var args = try std.process.argsWithAllocator(allocator);
    defer args.deinit();

    var cfg = Config{};
    _ = args.next();

    while (args.next()) |arg| {
        if (std.mem.eql(u8, arg, "--file")) {
            cfg.filename = args.next() orelse return error.MissingValue;
        } else if (std.mem.eql(u8, arg, "--level")) {
            const lvl = args.next() orelse return error.MissingValue;
            cfg.level = try LogLevel.parse(lvl);
        } // podobně
    }
    return cfg;
}

orelse return error.MissingValue nahrazuje 3 kontroly. Chyba je typizovaná.

Parsování logů: od malloc k error unions

C (~65 řádků):

LogEntry *parse_line(char *line) {
    LogEntry *entry = malloc(sizeof(LogEntry));
    if (!entry) return NULL;

    char *saveptr;
    char *token = strtok_r(line, " ", &saveptr);
    if (!token) { free(entry); return NULL; }

    // parsování timestamp, level, 40 řádků...
    return entry; // free na volajícím
}

Zig (~35 řádků):

const LogEntry = struct {
    timestamp: i64,
    level: LogLevel,
    source: []const u8,
    ip: []const u8,
    status: u16,
};

fn parseLine(line: []const u8) !LogEntry {
    var iter = std.mem.splitScalar(u8, line, ' ');

    const ts_str  = iter.next() orelse return error.InvalidFormat;
    const timestamp = try parseTimestamp(ts_str);

    const level_str = iter.next() orelse return error.InvalidFormat;
    const level = try LogLevel.parse(level_str);

    const source = iter.next() orelse return error.InvalidFormat;
    const ip     = iter.next() orelse return error.InvalidFormat;

    _ = iter.next(); // HTTP-metoda
    const status_str = iter.next() orelse return error.InvalidFormat;
    const status = try std.fmt.parseInt(u16, status_str, 10);

    return LogEntry{...};
}

Žádné malloc/free/strtok. Signatura !LogEntry zaručuje zpracování chyb.

Práce se soubory: defer v akci

pub fn processFile(
    allocator: std.mem.Allocator,
    path: []const u8,
    cfg: Config,
) !Stats {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();

    var buffered = std.io.bufferedReader(file.reader());
    var reader = buffered.reader();

    var stats = Stats.init(allocator);
    defer stats.deinit();

    var line_buf: [8192]u8 = undefined;

    while (try reader.readUntilDelimiterOrEof(&line_buf, '\n')) |line| {
        const entry = parseLine(line) catch continue;
        if (cfg.matches(entry)) {
            try stats.add(entry);
        }
    }
    return stats;
}

defer zavírá zdroje při jakémkoli ukončení. V C bylo potřeba fclose před každým return.

Benchmarky a metriky

Test: 5 milionů řádků, 800 MB.

| Metrika | C | Zig |

|------------------|--------|--------|

| Čas (sekundy) | 4,2 | 2,8 |

| Binárka (KB) | 47 | 32 |

| Paměť (MB) | 124 | 89 |

| Sestavení (sek.) | 3,1 | 1,4 |

| Řádků kódu | 3147 | 1089 |

Zrychlení díky bufferedReader vs fgets. Kód je 3krát kompaktnější.

Typické chyby při migraci

  • Allocators: page_allocator všude → ArenaAllocator pro CLI (deinit na konci).
  • Slices: []const u8 bez \0, pro C-API — .ptr + null-termination.
  • Kompilátor: dlouhé chyby, čti první řádek.
  • Proměnlivost: Zig 0.13, příklady 2022–2023 nekompilují — docs.ziglang.org.

Co je důležité

  • Zig redukuje CLI kód 3krát díky error unions a defer.
  • Žádné pády: typy paměti a absence skrytých alokací.
  • Rychlejší než C o 30–40 % při správné bufferizaci.
  • Ideální pro utility, embedded, křížovou kompilaci.
  • Pro velké projekty s ekosystémem — Rust je vhodnější.

— Editorial Team

Advertisement 728x90

Číst dál