Rustにおけるシングルトンの安全な実装方法

## はじめに

この記事は[Rustにおけるシングルトンの実装方法](https://qiita.com/suin/items/3592095336c326d2d122)を見て書いたものです。初めは記事にコメントしようかと思ったのですが、コメントでは長くなりすぎると思い記事化することにしました。

## TL; DR

[once_cell] 使え。
（将来stdでonce_cellが安定化したら）[LazyLock]使え。

[once_cell]: https://crates.io/crates/once_cell
[LazyLock]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html

## `unsafe` は安全な時にしか使ってはいけない

矛盾するようですが、 `unsafe` キーワードを使うのは安全性を保証できるときに限るべきです。
Rustの有効な（コンパイル可能な）コードには、以下の3種類があります。

1. 静的解析で安全性が保証できるコード
2. 静的解析で安全性が保証できないが、（事前条件を満たせば）ロジック的には安全であるコード
3. 静的解析で安全性が保証できないし、いかなる意味でも安全ではないコード

1のコードは `unsafe` ブロックを使わずに実装できます。2のコードと3のコードは `unsafe` ブロックの中にしか書けません。しかし、 **`unsafe` ブロックの中であっても、3のコードは書くべきではありません**。 `unsafe` というのはあくまで**安全だけど静的解析で安全性が保証できない処理**を行うために用意された穴であって、 `unsafe` ブロックの中なら何をやっても良いと捉えるのではなく、 `unsafe` ブロックの中に書いた処理の安全性は、コンパイラが保証してくれない分、プログラマが強く保証しなければならない、と捉えるべきです。

RustのStandard library developers Guideには[safety comments policy](https://std-dev-guide.rust-lang.org/documentation/safety-comments.html)という項目があります。`unsafe` 関数のドキュメントコメントには `# Safety` 節を書くことと、 `unsafe` ブロックの前には `SAFETY: ` を付けたコメントを書いて、安全に使うための事前条件や、なぜそれが安全なのかを明示するというポリシーです。もちろんこれは標準ライブラリの開発者ガイドラインにおけるポリシーであって全てに適用される訳ではありませんが、**これを書くことができないなら、それは真に安全な処理ではない**ということは肝に銘じておくべきです。

という前置きをした上で、元記事の

> Rustでは、`static`キーワードと`unsafe`ブロックを使用してシングルトンを実装することができます。

という文を読んでみましょう。当然、その安全性はどうやって保証するのか？　という疑問が浮かびます。
実際、元記事で提示されたコードには、安全ではない可能性が含まれています。

## `static mut` 変数のマルチスレッド安全性

`static` 変数はグローバルに一意な変数です。それはつまり複数のスレッドから参照されうる、ということです。
そのため、 `static mut` 変数に対するアクセスは `unsafe` になります。複数のスレッドからの書き込みや読み込みが競合しうるからです。
なお、 `mut` が付かない `static` 変数に対する読み込みアクセスだけなら `unsafe` にはなりません。書き込みが行われなければいくら同時に読み込みを行っても安全だからです。`Cell` のような内部可変パターンもあるじゃないか、と思う方もいると思いますが、 `static` 変数は `Sync` トレイトを実装した型でしか作れず、 `Cell` は `Sync` トレイトを実装していないので `static` 変数にすることができません。

元記事のコードでは、

```rs
pub fn singleton_integer() -> &'static SingletonInteger {
    unsafe {
        if SINGLETON_INTEGER.is_none() {
            SINGLETON_INTEGER = Some(SingletonInteger { value: 123 });
        }
        SINGLETON_INTEGER.as_ref().unwrap()
    }
}
```

という処理を行っています。 `SINGLETON_INTEGER` は `Option<SingletonInteger>` 型の変数なので、 「`None` または `Some` のフラグ」と「SingletonInteger一個分のメモリ領域」をメンバーに持つ構造です。この構造を

```rs
struct OptionalSingletonInteger {
    is_some: bool,
    value: MaybeUninit<SingletonInteger>,
}
```

という形で表現してみましょう。（これはあくまで `Option<SingletonInteger>` の内部構造を模式的に表すためのもので、実際にコンパイラがこのような構造で扱うことを保証するものではありません）

`SINGLETON_INTEGER` の型を上記の `OptionalSingletonInteger` に置き換えてみると、

```rs
pub fn singleton_integer() -> &'static SingletonInteger {
    unsafe {
        if !SINGLETON_INTEGER.is_some {
            SINGLETON_INTEGER.is_some = true;
            SINGLETON_INTEGER.value.write(SingletonInteger { value: 123 });
        }
        if SINGLETON_INTEGER.is_some {
            SINGLETON_INTEGER.assume_init_ref()
        } else {
            panic!();
        }
    }
}
```

という処理になります。

`SINGLETON_INTEGER` が未初期化の時、スレッドAとスレッドBで同時に `singleton_integer().value()` が呼ばれた場合を考えましょう。以下のようになる可能性があります。

1. スレッドAが `!SINGLETON_INTEGER.is_some` をチェック、 `true` だったので `{}` 内部に入る
2. スレッドAが `SINGLETON_INTEGER.is_some = true` を実行
3. スレッドBが `!SINGLETON_INTEGER.is_some` をチェック、 `false` だったので処理をスキップ
4. スレッドBが `SINGLETON_INTEGER.is_some` をチェック、 `true` だったので `{}` 内部に入る
5. スレッドBが `SINGLETON_INTEGER.assume_init_ref()` を実行、 `&'static SingletonInteger` の参照を返す
6. スレッドBが `value()` を実行
7. スレッドAが `SINGLETON_INTEGER.value.write(SingletonInteger { value: 123 });` を実行

この例だとスレッドBが `value()` を実行した時点で内部は初期化されていないので、未定義動作になります。
これはあくまでも一例で、必ずこうなる、という訳ではありません。実際には問題は複雑で、コンパイラによる命令のリオーダー、CPUのパイプラインによる命令のリオーダー、マルチコアCPUにおけるメモリコヒーレンスの問題など様々な落とし穴が存在します。
一方で、二つのスレッドから同時に書き込みを伴うアクセスが行われなければ問題は発生しないため、プログラムの構造によってはまず発生しない可能性もあり、同じようなコードが見過ごされていることも多いかもしれません。

## 安全なシングルトンを作る

という訳で、元記事のコードを安全なものに変えてみようと思います。
それにあたって、 [once_cell] クレートを導入します。
[`once_cell::sync::Lazy`](https://docs.rs/once_cell/1.17.1/once_cell/sync/struct.Lazy.html) は、初回アクセス時に一度だけ初期化を行う値を表現することができ、また、内部可変パターンの一種なため `mut` を使わずに利用することができるため、 `unsafe` ブロックを使わずに書くことができます。

```rs
use derive_getters::Getters;
use once_cell::sync::Lazy;

#[derive(Debug, Getters)]
pub struct SingletonInteger {
    value: i64,
}

pub fn singleton_integer() -> &'static SingletonInteger {
    &*SINGLETON_INTEGER
}

static SINGLETON_INTEGER: Lazy<SingletonInteger> = Lazy::new(|| SingletonInteger { value: 123 });
```

また、 once_cellクレートと同様の機能は標準ライブラリにも提案されており、 `once_cell::sync::Lazy` は
 [`std::sync::LazyLock`][LazyLock] として実装予定です。

## おわりに

この記事を途中まで書いていたら、

[RustでSingletonを実現する方法](https://qiita.com/kujirahand/items/d7f6bae84a66ab4c783d)

が投稿されていました。こちらの記事の方が `Mutex` を使った書き込みもできるパターンに触れていたり、once_cellクレート以前に良く使われていたlazy_staticクレートに触れていたりと網羅的な内容で、もう投稿しなくてもいいかなとも思ったのですが、せっかく書いたし勿体ないので投稿します。