Go 1.21.0 で導入された遅延初期化関数 sync.OnceValue

2023/09/19に公開

Go 1.21.0 のマイナーなライブラリ修正の中に、sync.OnceValue という関数が追加されていた。[1]

sync

The new OnceFunc, OnceValue, and OnceValues functions capture a common use of Once to lazily initialize a value on first use.

どうやら、これは遅延初期化を実現するもののようだ。

「遅延初期化」とは、初期化処理を使用する直前に1回だけ行うというもので

  • 実際に使用するかしないか分からないので、起動時のタイミングで行うと初期化コストが無駄になる場合がある
  • 使用する直前でないと、初期化に必要な情報がそろいにくい
  • 使用するたびに初期化というのはコストがかかりすぎる
  • 複数個所のどこで最初に呼び出されるのか分からないので、初期化関係の煩わしい手続きは呼び出される側で完結させたい

ようなオブジェクトに便利な機能である。

package main

import (
    "sync"
)

var s1 = sync.OnceValue(func() string {
    println("s1 initialize")
    return "Foo"
})

func main() {
    println("start")
    println(s1())
    println(s1())
}
$ go run main.go
start
s1 initialize
Foo
Foo

プロセス起動時に sync.OnceValue 関数を呼ぶので、遅延初期化といっても起動時コストはまったくのゼロというわけでもない。

以前、自作した遅延初期化ライブラリでは、次のように起動時にロジックを動かさない静的な遅延初期化も使えるようにしていた。

package main

import (
    "github.com/hymkor/go-lazy"
)

var s1 = lazy.Of[string]{
    New: func() string {
        println("s1 initialize")
        return "Foo"
    },
}

func main() {
    println("start")
    println(s1.Value())
    println(s1.Value())
}

コスト的には有利だが、標準のものの方が初期化の際に発生する panic などへの対処なども適切にされており、今後 1.21 ベースで開発を行う場合は 基本的に sync.OnceValue の方を使うことになりそうだ。

脚注
  1. 他に sync.OnceFunc, sync.OnceValues という関数もあわせて追加されている。sync.OnceValues はエラーが発生しうる初期化関数などで error 値を合わせて返すのに使うことがあるが、sync.OnceFunc は…使い道が未だよく分からない ↩︎

GitHubで編集を提案

Discussion