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ů.
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", .{});
}
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í).
fn multipleDefers() void {
defer std.debug.print("First defer\n", .{});
defer std.debug.print("Second defer\n", .{});
defer std.debug.print("Third defer\n", .{});
}
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", .{});
}
Inner scope
Inner defer
Outer scope
Outer defer
defer ve smyčce
defer uvnitř smyčky se provede na konci každé iterace:
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
}
Test: defermut1.zig
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ě.
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
-
Použijte
deferpro 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čí. -
Použijte
errdeferpro částečný úklid: Použijteerrdefer, když potřebujete uklidit prostředky pouze v případě, že dojde k chybě, obvykle v konstruktorech nebo inicializačních funkcích. -
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.
-
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.
-
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.
-
Použití pro více než pouhou správu zdrojů: Zatímco se běžně používá pro úklid zdrojů,
deferaerrdefermohou být užitečné pro protokolování, obnovu stavu a další průřezové záležitosti. -
Kombinujte s alokátory: Zigův alokační vzor funguje dobře s
deferaerrdeferpro správu paměti. -
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ě.