V Zigu jsou správa zdrojů a zpracování chyb kritickými aspekty psaní robustního a efektivního kódu. Klíčová slova defer (odložit na konec) a errdefer (odložit při chybě) jsou výkonnými nástroji, které řeší tyto problémy a nabízejí elegantní řešení pro zajištění řádného úklidu při manipulaci se zdroji. Tato příručka se ponoří hluboko do jejich funkcí, případů použití a osvědčených postupů.

detailed defer

defer podrobně

Základní koncept

Klíčové slovo defer (odložit) v Zigu vám umožňuje naplánovat spuštění části kódu, když aktuální rozsah skončí, bez ohledu na to, jak se ukončí (normální návrat, chyba nebo panika).

fn exampleDefer() void {
    std.debug.print("Start of function\n", .{});
    defer std.debug.print("End of function\n", .{});
    std.debug.print("Middle of function\n", .{});
}
výstup
Start of function
Middle of function
End of function

Vícenásobné použití defer a pořadí vykonávání

Je-li použito více příkazů odložení (defer), jsou prováděny v opačném pořadí (poslední je první a první je poslední).

multiple defer

fn multipleDefers() void {
    defer std.debug.print("First defer\n", .{});
    defer std.debug.print("Second defer\n", .{});
    defer std.debug.print("Third defer\n", .{});
}
výstup
Third defer
Second defer
First defer

defer ve vnořených rozsazích

Příkazy defer jsou svázany se svým rozsahem:

fn nestedDefers() void {
    defer std.debug.print("Outer defer\n", .{});
    {
        defer std.debug.print("Inner defer\n", .{});
        std.debug.print("Inner scope\n", .{});
    }
    std.debug.print("Outer scope\n", .{});
}
výstup
Inner scope
Inner defer
Outer scope
Outer defer

defer ve smyčce

defer uvnitř smyčky se provede na konci každé iterace:

defer loop

fn deferInLoop() void {
    var i: usize = 0;
    while (i < 3) : (i += 1) {
        defer std.debug.print("End of iteration {}\n", .{i});
        std.debug.print("Iteration {}\n", .{i});
    }
}
Iteration 0
End of iteration 0
Iteration 1
End of iteration 1
Iteration 2
End of iteration 2

Pokročile použití defer

defer a proměnné

defer zachycuje aktuální hodnotu proměnných, nikoli jejich konečnou hodnotu:

fn deferWithMutable() void {
    var x: i32 = 10;
    defer std.debug.print("x = {}\n", .{x});
    x = 20;
    // tady se vykoná defer
}
výstup
jirka@pt3610:~/vyuka_sspvc/pmp/zig/ziglang$ zig run defermut1.zig
Volání deferWithMutable()
x = 20
jirka@pt3610:~/vyuka_sspvc/pmp/zig/ziglang$

Tohle je špatně, jak ukazuje výstup, kde je vidět konečná hodnota 20. Tedy defer bere konečnou hodnotu 20 a ne hodnotu v okamžiku příkazu, kde je x = 10. Je to proto, že defer se vykonává na konci.

defer při zpracování chyb

defer je zvláště užitečné pro zajištění úklidu ve funkcích, které mohou vracet chyby:

fn processWithDefer() !void {
    var resource = try acquireResource();
    defer releaseResource(resource);

    try doSomething(resource);
    try doSomethingElse(resource);
    // Resource is released even if doSomething or doSomethingElse returns an error
}

Pokročilé použití errdefer

Základní koncept

errdefer je podobné jako defer, ale spustí se pouze tehdy, když rozsah skončí kvůli chybě.

errdefer concept

fn exampleErrdefer() !void {
    var resource = try acquireResource();
    errdefer releaseResource(resource);

    try riskyOperation(resource);
    // If riskyOperation fails, resource is released
    // If it succeeds, resource is not released here
}

Kombinování defer a errdefer

Můžete použít defer i errdefer ve stejné funkci pro různé účely:

fn complexFunction() !void {
    var resource1 = try acquireResource1();
    defer releaseResource1(resource1);

    var resource2 = try acquireResource2();
    errdefer releaseResource2(resource2);

    try riskyOperation(resource1, resource2);
    // resource1 is always released
    // resource2 is only released if riskyOperation fails
}

errdefer ve smyčkách

errdefer ve smyčce se spustí pouze v případě, že smyčka skončí kvůli chybě:

fn errdeferInLoop() !void {
    var i: usize = 0;
    errdefer std.debug.print("Loop failed at iteration {}\n", .{i});

    while (i < 5) : (i += 1) {
        if (i == 3) return error.LoopFailed;
    }
}

Pokročilé použití errdefer

errdefer s podmínkou

fn conditionalErrdefer(condition: bool) !void {
    var resource = try acquireResource();
    errdefer if (condition) releaseResource(resource);

    try riskyOperation(resource);
    // Resource is only released on error if condition is true
}

errdefer s anonymní funkcí

Pro složitější logiku čištění můžete použít anonymní funkce s errdefer:

fn complexErrdefer() !void {
    var resource = try acquireResource();
    errdefer {
        releaseResource(resource);
        std.debug.print("Resource released due to error\n", .{});
    }

    try riskyOperation(resource);
}

Doporučené postupy a úvahy

  1. Použijte defer pro zaručený úklid: Vždy používejte odložení (defer) pro prostředky, které je třeba uklidit, bez ohledu na to, jak se funkce ukončí.

  2. Použijte errdefer pro částečný úklid: Použijte errdefer, když potřebujete uklidit prostředky pouze v případě, že dojde k chybě, obvykle v konstruktorech nebo inicializačních funkcích.

  3. Udržujte odložené akce jednoduché: Vyhněte se složité logice v odložených příkazech. Pokud je potřeba komplexní úklid, zvažte vytvoření samostatné funkce.

  4. Buďte si vědomi exekučního příkazu: Pamatujte, že odložené příkazy se provádějí v opačném pořadí než jejich deklarace.

  5. Vyhněte se vedlejším účinkům: Odložené příkazy by obecně měly zabránit vedlejším účinkům, které ovlivňují logiku funkce.

  6. Použití pro více než pouhou správu zdrojů: Zatímco se běžně používá pro úklid zdrojů, defer a errdefer mohou být užitečné pro protokolování, obnovu stavu a další průřezové záležitosti.

  7. Kombinujte s alokátory: Zigův alokační vzor funguje dobře s defer a errdefer pro správu paměti.

  8. Testujte chybové cesty: Ujistěte se, že jste otestovali chybové cesty ve funkcích pomocí errdefer, abyste ověřili, že úklid probíhá správně.

Zdroje a odkazy