ざっくりZig - 関数の終了処理(defer, errdefer, unreachable, @panic)
Zigでは関数の実行が終了する際に行う処理を事前に設定できます。defer
は関数の終了が正常か否かに拘わらず必ず行う処理を設定します。一方errdefer
は終了時にエラーが返される際に行う処理を設定します。ただし、unreachable
でプログラムが終了するときはそれらで設定した処理が実行されませんので注意が必要です。
defer
defer
は関数の終了後に行う処理を事前に設定するものです。1つの関数の中に複数存在できますが、その場合は後に設定された処理から先に実行されます。
const std = @import("std");
const print = std.debug.print;
pub fn main() void {
defer print("main: defer1\n", .{});
defer print("main: defer2\n", .{});
defer print("main: defer3\n", .{});
}
main: defer3
main: defer2
main: defer1
設定は{...}
によるブロックでも可能できます。この場合、ブロック内部の実行順はソースコードの順と同じです。
const std = @import("std");
const print = std.debug.print;
pub fn main() void {
defer {
print("main: defer1-1\n", .{});
print("main: defer1-2\n", .{});
}
defer {
print("main: defer2-1\n", .{});
print("main: defer2-2\n", .{});
}
}
main: defer2-1
main: defer2-2
main: defer1-1
main: defer1-2
errdefer
errdefer
は関数がエラーを返すときに実行する処理を設定する点がdefer
と異なります。その他はdefer
と同じです。ただし、関数の呼び出しがtry
で行われたときは呼び出し元のerrdefer
で設定された処理も実行されますが、catch
でエラー処理が行われる場合は呼び出し元のerrdefer
で設定した処理は実行されません。
const std = @import("std");
const print = std.debug.print;
pub fn f() !void {
defer print("f: defer\n", .{});
errdefer print("f: errdefer\n", .{});
return error.Something;
}
pub fn main() !void {
defer print("main: defer\n", .{});
errdefer print("main: errdefer\n", .{});
// 以下の違いでerrdeferの処理が実行されるかが変わる
// try f();
// f() catch |err| print("catch: {any}\n", .{err});
}
f: errdefer
f: defer
main: errdefer (実行された)
main: defer
error: Something
// Debugモードで実行すると以下にエラーメッセージを表示
f: errdefer
f: defer
catch: error.Something (mainのerrdeferは実行されていない)
main: defer
unreachableとdefer, errdefer
unreachable
は「この後は何も実行しない」という意味で、コンパイルオプション(-O
)がDebug
かReleaseSafe
のときはpanic
となり、ReleaseFast
かReleaseSmall
のときはここでプログラムが終了します。
このとき、unreachable
の前にdefer
やerrdefer
があっても、これらで設定した処理は実行されません。
const std = @import("std");
const print = std.debug.print;
pub fn g(flag: bool) !void {
defer print("g: defer\n", .{});
errdefer print("g: errdefer\n", .{});
if (flag) return error.Something;
}
pub fn f() !void {
defer print("f: defer\n", .{});
errdefer print("f: errdefer\n", .{});
g(true) catch unreachable; // この後はdefer, errdefer含め何も実行されない
return error.Something;
}
pub fn main() !void {
defer print("main: defer\n", .{});
errdefer print("main: errdefer\n", .{});
try f();
}
g: errdefer
g: defer
@panic
@panic
は引数の文字列を出力したあと強制的にpanic
処理(コアダンプ)が行わわれてプログラムが終了します。このとき、unreachable
と同様defer
, errdefer
の処理は実行されません。なお、コンパイルオプションによって引数の文字列以外に出力される情報が以下のように異なります。
{ コンパイルオプション(-O ) |
引数の文字列以外の出力 |
---|---|
Debug | トレース情報(@panic の位置から呼び出し元まで) |
ReleaseSmall | トレース情報なし |
RelaseFast, ReleaseSafe |
@panic の位置もしくはmain 関数の実行位置 |
const std = @import("std");
const print = std.debug.print;
pub fn g(flag: bool) !void {
defer print("g: defer\n", .{});
errdefer print("g: errdefer\n", .{});
if (flag) @panic("Panic!!!"); // 強制的にpanic(コアダンプ)
}
pub fn f() !void {
defer print("f: defer\n", .{});
errdefer print("f: errdefer\n", .{});
g(true) catch unreachable;
return error.Something;
}
pub fn main() !void {
defer print("main: defer\n", .{});
errdefer print("main: errdefer\n", .{});
try f();
}
// Debug
thread 2651 panic: Panic!!!
/path/to/source.zig:8:15: 0x1033c02 in g (source)
if (flag) @panic("Panic!!!");
^
/path/to/source.zig:14:6: 0x1033e75 in f (source)
g(true) catch unreachable;
^
/path/to/source.zig:21:10: 0x1034350 in main (source)
try f();
^
/path/to/zig/lib/std/start.zig:511:37: 0x1033b05 in posixCallMainAndExit (source)
const result = root.main() catch |err| {
^
/path/to/zig/lib/std/start.zig:253:5: 0x1033621 in _start (source)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
中止 (コアダンプ)
// ReleaseFast
thread 2597 panic: Panic!!!
/path/to/source.zig:8:15: 0x1008f24 in main (source)
if (flag) @panic("Panic!!!");
^
/path/to/zig/lib/std/start.zig:511:37: 0x1008f04 in posixCallMainAndExit (source)
const result = root.main() catch |err| {
^
/path/to/zig/lib/std/start.zig:253:5: 0x1008e31 in _start (source)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
中止 (コアダンプ)
// ReleaseSafe
thread 2626 panic: Panic!!!
/path/to/source.zig:8:15: 0x100a1a4 in main (source)
if (flag) @panic("Panic!!!");
^
/path/to/zig/lib/std/start.zig:511:37: 0x100a171 in posixCallMainAndExit (source)
const result = root.main() catch |err| {
^
/path/to/zig/lib/std/start.zig:253:5: 0x100a021 in _start (source)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
中止 (コアダンプ)
// ReleaseSmall
thread 2545 panic: Panic!!!
Unable to dump stack trace: debug info stripped
中止 (コアダンプ)
まとめ
-
defer
は関数の終了後に行う処理を設定する- 後に設定された処理から先に実行する
- 一文もしくはブロックで設定できる
-
unreachable
でプログラムが終了すると実行されない
-
errdefer
は関数からエラーが返されるときの処理を設定する以外はdefer
と同じ -
@panic
はプログラム内で強制的にpanic処理を行い、設定したメッセージを出力する- コンパイルオプションによって出力される情報が異なる
< エラー(error, try, catch, if...else, ビルトイン関数)
ポインタとスライス(*T, *[N]T, [*]T, []T, &変数, &関数) >
ざっくりZig 一覧
Discussion