Zen言語の標準ライブラリ紹介〜lazy〜

はじめに

けっこう標準ライブラリが充実しているわけですが、ドキュメントがないのがもったいないですね。 まとまった時間が取れないので、ちょこちょこ書いていくシリーズです。

リクエストあれば、優先する、かも?

グローバルなデータをスレッド安全に初期化するLazyInitです。

std.lazy.LazyInit

コンパイル時に初期値が決まらないグローバルデータを、安全に初期化するのは意外と面倒です。 LazyInitはそのような場合に、安全にグローバルデータを初期化することができます。

lazy.init() は任意の型Tに対して、LazyInit(T)の値を返します。 この時、T型の値は未定義です。

pub fn init(comptime T: type) LazyInit(T)

LazyInitget()resolve()の2つのメソッドを持ちます。

// LazyInit(T) {
    /// Returns a usable pointer to the initialized data,
    /// or returns null, indicating that the caller should
    /// perform the initialization and then call resolve().
    pub fn get(self: *Self) ?Ptr


    pub fn resolve(self: *Self) void

get()null が返ってきたら、値 (data フィールド) を初期化して、resolve() を呼びます。 内部は簡単なステートマシンを持っており、resolve()を呼ぶと、初期化済み状態になります。

const std = @import("std");
const lazy = std.lazy;
const testing = std.testing;

var global = lazy.init(u32);

test "std.lazy.LazyInit" {
    if (global.get()) |value| {
        // `resolve()`を呼ぶまでは `null` が返るので、ここには到達しない
        unreachable;
    } else {
        // `null` が取得できた場合、データを初期化して、`resolve`を呼ぶ
        global.data = 42;
        global.resolve();
    }

    if (global.get()) |value| {
        // get returns `*T`
        testing.equal(@to(u32, 42), value.*);
    } else {
        unreachable;
    }
}

注意点としては、get()nullが返ってきたら、必ず初期化しないといけない、ということです。 次のコードは無限ループに陥ります。

test "deadlock" {
    const S = struct {
        var g = lazy.init(u32);
    };
    if (S.g.get()) |_| {}
    if (S.g.get()) |_| {}
}

個人的には、lazy.init()が初期化関数を受け取れるようになっていて、最初に呼ばれたget()内で初期化関数呼び出してくれる方が良いなぁ、と思ったりします。