defer
/ errdefer
を使うことで、特定の処理をスコープを抜けるタイミングまで遅延させることができます。defer
/ errdefer
はしばしばリソース解放に使用されます。defer
/ errdefer
を使用し、リソース解放処理を容易に記述することができます。
defer
は特定の処理をそのスコープを抜けるタイミングで実行します。defer
キーワードから始まり、式;
またはブロック ({}
) が続きます。
次のコードでは、A
を出力するdefer
文の方が、B
を出力する文より前に書かれています。
examples/ch04-basic-syntax/src/defer.zen:5:8
test "defer" {
defer std.debug.warn("A\n", .{});
std.debug.warn("B\n", .{});
}
それにも関わらず、このコードの実行結果は次の通り、B
が先に出力されます。
B
A
複数のdefer
文がある場合、より後ろに書かれているdefer
から逆順に実行されます。
examples/ch04-basic-syntax/src/defer.zen:10:15
test "execution order of defers" {
defer std.debug.warn("A\n", .{});
defer std.debug.warn("B\n", .{});
defer std.debug.warn("C\n", .{});
defer std.debug.warn("D\n", .{});
}
実行結果は、次の通りです。
D
C
B
A
defer
の一番の使いみちは、リソース解放です。例えば、次のコードを考えてみます。これは特に意味のあるコードではありませんが、メモリリークがどのような状況で発生するかを説明するには十分です。メモリアロケータについて8章 メモリ管理で説明していますが、ここではconst allocated = try heap.create(allocator, u32);
でヒープ領域のメモリを動的確保し、heap.destroy(allocator, allocated);
で確保したメモリ領域を解放していることだけ、頭の片隅に置いておいて下さい。
examples/ch04-basic-syntax/src/defer.zen:17:28}}
fn memoryLeak(input: u64) !void {
const allocator = heap.page_allocator;
const allocated = try heap.create(allocator, u32);
if (input == 0) {
// `allocated`のメモリ解放漏れ!
return error.IsZero;
}
// 何かやる
heap.destroy(allocator, allocated);
return;
}
このように、メモリの確保と解放とが離れた場所にあると、容易にメモリリークが発生してしまいます。
そこで、deferを使って、メモリ確保のコードの直後にメモリ解放処理を書きます。
examples/ch04-basic-syntax/src/defer.zen:34:50
fn releaseWithDefer(input: u64) !void {
const allocator = heap.page_allocator;
// メモリ確保の直後にdeferで解放処理を書く
const allocated = try heap.create(allocator, u64);
defer {
heap.destroy(allocator, allocated);
std.debug.warn("heap destroyed!\n", .{});
}
if (input == 0) {
// スコープを抜ける時に`defer`が実行される
return error.IsZero;
}
// 何かやる
// スコープを抜ける時に`defer`が実行される
return;
}
次のテストを書いて、コードを実行してみましょう。
examples/ch04-basic-syntax/src/defer.zen:52:54
test "releaseWithDefer" {
releaseWithDefer(0) catch |_| {};
}
次のメッセージが出力されるはずです。
heap destroyed!
複数行にわたらない場合は、次のように書くこともできます。
defer heap.destroy(allocator, allocated);
errdefer
はdefer
と似ていますが、関数がエラーを返す場合のみ実行される点が、defer
と異なります。
例えば、リソースを確保し、リソースに対して何らかの処理を行ってから、そのリソースを上位関数に返すことを考えます。確保したリソースに対する処理は、失敗する場合があります。
このような典型的な例は、ネットワークにおけるソケット通信の開始でしょう。以下に、Zenの標準ライブラリstd.net.connectUnixSocket
関数を簡略化した例を示します (このコードはコンパイルできません) 。
/// simplified std.net.connectUnixSocket
const os = std.os;
fn connectUnixSocket(path: []u8) !std.fs.File {
// ソケットファイルをオープンする
const sockfd = try os.socket(
os.AF_UNIX,
os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK,
0,
);
errdefer os.close(sockfd);
var sock_addr = os.sockaddr{
// ...
};
// 中略
const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
try os.connect(sockfd, &sock_addr, size);
return std.fs.File.openHandle(sockfd);
}
まずソケットのファイルをオープンし (os.socket
) 、その後、ソケットを接続しています (os.connect
) 。接続できると、ファイルはオープンしたまま、そのファイルディスクリプタのハンドラを上位関数に返す、というのが標準のフローです。
しかし、os.connect
は失敗し、エラーを返す場合があります。その場合、オープンしたファイルはクローズしなければなりません。繰り返しますが、標準フローの場合は、ファイルをオープンにしたままにしなければなりません。
そこでerrdefer
の登場です。errdefer os.close(sockfd);
を書いておくことで、os.connect
が失敗した場合のみ、ファイルをクローズします。
errdefer
ブロックでは、戻り値となるエラー種別をキャプチャすることができます。
examples/ch04-basic-syntax/src/errdefer.zen:4:13
fn foo() !i32 {
errdefer |err| {
ok(err == error.One);
}
return error.One;
}
test "errdefer with payload" {
_ = foo() catch return;
}
☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.