Rust 100 Ex 🏃【27/37】 スレッド・'staticライフタイム ～並列処理に見るRustの恩恵～

前の記事

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1) ← 初回
- ...
- [【26】 HashMap・順序・BTreeMap ～Rustの辞書型～](https://qiita.com/namn1125/items/a2806cded12fa719a1cb) ← 前回
- 【27】 スレッド・'staticライフタイム ～並列処理に見るRustの恩恵～ ← 今回

<details><summary>全記事一覧</summary>

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1)
- [【1】 構文・整数・変数](https://qiita.com/namn1125/items/c776768baec89b8a0438)
- [【2】 if・パニック・演習](https://qiita.com/namn1125/items/13870965e8273bc87bce)
- [【3】 可変・ループ・オーバーフロー](https://qiita.com/namn1125/items/4f57f8aede2f5201016f)
- [【4】 キャスト・構造体 (たまにUFCS)](https://qiita.com/namn1125/items/227ed5bbedbb775c5d27)
- [【5】 バリデーション・モジュールの公開範囲 ～ → カプセル化！～](https://qiita.com/namn1125/items/46721cceccbfa9ec3c7a)
- [【6】 カプセル化の続きと所有権とセッター ～そして不変参照と可変参照！～](https://qiita.com/namn1125/items/442de7798d49f777384c)
- [【7】 スタック・ヒープと参照のサイズ ～メモリの話～](https://qiita.com/namn1125/items/780d80e4340f0e34dbf6)
- [【8】 デストラクタ(変数の終わり)・トレイト ～終わりと始まり～](https://qiita.com/namn1125/items/7247edde3b6bd0b85b25)
- [【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ～Empowerment 💪 ～](https://qiita.com/namn1125/items/e565713b423301a80c76)
- [【10】 トレイト境界・文字列・Derefトレイト ～トレイトのアレコレ～](https://qiita.com/namn1125/items/9060faee2965f23e0e3d)
- [【11】 Sized トレイト・From トレイト・関連型 ～おもしろトレイトと関連型～](https://qiita.com/namn1125/items/b68990712d379a3d6a49)
- [【12】 Clone・Copy・Dropトレイト ～覚えるべき主要トレイトたち～](https://qiita.com/namn1125/items/c6f34945f331c7229746)
- [【13】 トレイトまとめ・列挙型・match式 ～最強のトレイトの次は、最強の列挙型～](https://qiita.com/namn1125/items/6b2cc31d6a47f9ffaff6)
- [【14】 フィールド付き列挙型とOption型 ～チョクワガタ～](https://qiita.com/namn1125/items/6fe69d1cab6c7936c698)
- [【15】 Result型 ～Rust流エラーハンドリング術～](https://qiita.com/namn1125/items/698aafaf22ec4bfd1bde)
- [【16】 Errorトレイトと外部クレート ～依存はCargo.tomlに全部お任せ！～](https://qiita.com/namn1125/items/9dae7b728fa0668e4102)
- [【17】 thiserror・TryFrom ～トレイトもResultも自由自在！～](https://qiita.com/namn1125/items/d67006a29b7ff5d6148c)
- [【18】 Errorのネスト・慣例的な書き方 ～Rustらしさの目醒め～](https://qiita.com/namn1125/items/705829027661a38e4a4f)
- [【19】 配列・動的配列 ～スタックが使われる配列と、ヒープに保存できる動的配列～](https://qiita.com/namn1125/items/9c49fd4c08f3e61896c7)
- [【20】 動的配列のリサイズ・イテレータ ～またまたトレイト登場！～](https://qiita.com/namn1125/items/6e4dede789c6de97f396)
- [【21】 イテレータ・ライフタイム ～ライフタイム注釈ようやく登場！～](https://qiita.com/namn1125/items/9a1224abf733f29a980a)
- [【22】 コンビネータ・RPIT ～ :dragon: 「 `Iterator` トレイトを実装してるやつ」～](https://qiita.com/namn1125/items/27fe20f2d60803c94357)
- [【23】 `impl Trait` ・スライス ～配列の欠片～](https://qiita.com/namn1125/items/7601d0268293ce7fcf28)
- [【24】 可変スライス・下書き構造体 ～構造体で状態表現～](https://qiita.com/namn1125/items/a9acc7dc2ca7c4c55cc3)
- [【25】 インデックス・可変インデックス ～インデックスもトレイト！～](https://qiita.com/namn1125/items/bd43375362af10ea8b42)
- [【26】 HashMap・順序・BTreeMap ～Rustの辞書型～](https://qiita.com/namn1125/items/a2806cded12fa719a1cb)
- [【27】 スレッド・'staticライフタイム ～並列処理に見るRustの恩恵～](https://qiita.com/namn1125/items/5a4a09172834063268b1)
- [【28】 リーク・スコープ付きスレッド ～ライフタイムに技あり！～](https://qiita.com/namn1125/items/34a0aacd4d40f9234aef)
- [【29】 チャネル・参照の内部可変性 ～Rustの虎の子、mpscと `Rc<RefCell<T>>`～](https://qiita.com/namn1125/items/861e72dbb4b837508948)
- [【30】 双方向通信・リファクタリング ～返信用封筒を入れよう！～](https://qiita.com/namn1125/items/46f03de967d0de46737e)
- [【31】 上限付きチャネル・PATCH機能 ～パンクしないように制御！～](https://qiita.com/namn1125/items/d7ae8a205ee969339401)
- [【32】 `Send`・排他的ロック(`Mutex`)・非対称排他的ロック(`RwLock`) ～真打 `Arc<Mutex<T>>` 登場～](https://qiita.com/namn1125/items/eee890dc8672e975e70c)
- [【33】 チャネルなしで実装・Syncの話 ～考察回です～](https://qiita.com/namn1125/items/cc8bee3871b727e564e3)
- [【34】 `async fn`・非同期タスク生成 ～Rustの非同期入門～](https://qiita.com/namn1125/items/9c32b730a283ad76c891)
- [【35】 非同期ランタイム・Futureトレイト ～非同期のお作法～](https://qiita.com/namn1125/items/c840d48f8f558613ec10)
- [【36】 ブロッキング・非同期用の実装・キャンセル ～ラストスパート！～](https://qiita.com/namn1125/items/39c139da3f1f26264353)
- [【37】 Axumでクラサバ！ ～最終回～](https://qiita.com/namn1125/items/bae492f823fbe60e9974)
- [【おまけ1】 Rustで勘違いしていたこと3選 🏄🌴 【100 Exercises To Learn Rust 🦀 完走記事 🏃】](https://qiita.com/namn1125/items/2a59f486cce30fd2b889)
- [【おまけ2】 【🙇 懺悔 🙇】Qiitanグッズ欲しさに1日に33記事投稿した話またはQiita CLIとcargo scriptを布教する的な何か](https://qiita.com/namn1125/items/8e552694b389c5b13b5e)
</details>

[100 Exercise To Learn Rust](https://rust-exercises.com/) 演習第27回になります、今回から新章7章、スレッドの話題です！

今回の関連ページ

- [7.1. Threads](https://rust-exercises.com/07_threads/01_threads)
- [7.2. 'static lifetime](https://rust-exercises.com/07_threads/02_static)

# [[07_threads/01_threads](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/07_threads/01_threads/src/lib.rs)] スレッド(並列処理)

- [7.1. Threads](https://rust-exercises.com/07_threads/01_threads)

問題はこちらです。

```rust:lib.rs
// TODO: implement a multi-threaded version of the `sum` function
//  using `spawn` and `join`.
//  Given a vector of integers, split the vector into two halves and
//  sum each half in a separate thread.

// Caveat: We can't test *how* the function is implemented,
// we can only verify that it produces the correct result.
// You _could_ pass this test by just returning `v.iter().sum()`,
// but that would defeat the purpose of the exercise.
//
// Hint: you won't be able to get the spawned threads to _borrow_
// slices of the vector directly. You'll need to allocate new
// vectors for each half of the original vector. We'll see why
// this is necessary in the next exercise.
use std::thread;

pub fn sum(v: Vec<i32>) -> i32 {
    todo!()
}
```

TODO部分を要約すると次のような感じです。

- TODO: [`thread::spanw`](https://doc.rust-lang.org/std/thread/fn.spawn.html) と [`JoinHandle::join`](https://doc.rust-lang.org/std/thread/struct.JoinHandle.html#method.join) を使用する形で `sum` 関数のマルチスレッド版を作成してください
- 整数のベクタが渡されるので、これらを2つに分けて合計を計算した後、その2つを足し合わせるようにします

- 警告: 調べるすべがないのでスレッド使わないで普通に足し合わせてもパスできるけど、それだと演習の意味がないからやめてね
- ヒント: ベクトルをスライスにして参照をスレッドに渡すことはできません。この件については次の問題([本記事の2問目](#07_threads02_static-static-%E3%83%A9%E3%82%A4%E3%83%95%E3%82%BF%E3%82%A4%E3%83%A0))の範囲です
    - (とりあえず、)新しいベクトルを作成しましょう

<details><summary>テストを含めた全体</summary>

```rust:lib.rs
// TODO: implement a multi-threaded version of the `sum` function
//  using `spawn` and `join`.
//  Given a vector of integers, split the vector into two halves and
//  sum each half in a separate thread.

// Caveat: We can't test *how* the function is implemented,
// we can only verify that it produces the correct result.
// You _could_ pass this test by just returning `v.iter().sum()`,
// but that would defeat the purpose of the exercise.
//
// Hint: you won't be able to get the spawned threads to _borrow_
// slices of the vector directly. You'll need to allocate new
// vectors for each half of the original vector. We'll see why
// this is necessary in the next exercise.
use std::thread;

pub fn sum(v: Vec<i32>) -> i32 {
    todo!()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty() {
        assert_eq!(sum(vec![]), 0);
    }

    #[test]
    fn one() {
        assert_eq!(sum(vec![1]), 1);
    }

    #[test]
    fn five() {
        assert_eq!(sum(vec![1, 2, 3, 4, 5]), 15);
    }

    #[test]
    fn nine() {
        assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), 45);
    }

    #[test]
    fn ten() {
        assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 55);
    }
}
```
</details>

「2つのスレッドを建てて合計計算を並行処理して最後に足し合わせて」という問題ですね。

## 解説

`thread::spawn` にてスレッドを生成し、 `t1.join()` でスレッド内の処理が完了するのを待ち、最後にスレッドが返してきた値を足し合わせます。

```rust:lib.rs (コンパイルエラー！)
pub fn sum(v: Vec<i32>) -> i32 {
    let (v1, v2) = v.split_at(v.len() / 2);

    let t1 = thread::spawn(|| v1.iter().sum::<i32>());
    let t2 = thread::spawn(|| v2.iter().sum::<i32>());

    let res1 = t1.join().unwrap();
    let res2 = t2.join().unwrap();

    res1 + res2
}
```

スライスの [`split_at`](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at) 等を使って配列を分割すると、元の配列への参照になってしまいコンパイルエラーになります。

```plain:エラー内容
   Compiling threads v0.1.0 (/path/to/100-exercises-to-learn-rust/exercises/07_threads/01_threads)
error[E0597]: `v` does not live long enough
   --> exercises/07_threads/01_threads/src/lib.rs:104:20
    |
99  | pub fn sum(v: Vec<i32>) -> i32 {
    |            - binding `v` declared here
...
104 |     let (v1, v2) = v.split_at(v.len() / 2);
    |                    ^ borrowed value does not live long enough
105 |
106 |     let t1 = thread::spawn(|| v1.iter().sum::<i32>());
    |              ---------------------------------------- argument requires that `v` is borrowed for `'static`
...
113 | }
    | - `v` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `threads` (lib test) due to 1 previous error

 *  The terminal process "cargo 'test', '--package', 'threads', '--lib', '--', 'tests', '--show-output'" failed to launch (exit code: 101). 
 *  Terminal will be reused by tasks, press any key to close it. 
```

> <ruby>祈手<rt>アンブラハンズ</rt></ruby>「あの方は<ruby>今はいない<rt>・・・・・</rt></ruby>...<ruby>層<rt>スレッド</rt></ruby><ruby>を跨ぐやり方は不都合が出やすい<rt>・・・・・・・・・・・・・・・</rt></ruby>」

スレッドもそして次章8章の非同期処理にも登場する基本的な考えの一つとして、「参照類の `'static` ライフタイムではない値はそのままだとスレッドを跨げない」というものがあります。次の問題の解説で改めて紹介したいと思います。

とりあえず、新しく所有権を握れる構造体を作り、参照ではなく実体を渡すようにしてこの問題を回避します。 [`split_off` メソッド](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.split_off) や [`drain` メソッド](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain) を使うといい感じに書けます。

```rust:lib.rs
pub fn sum(mut v: Vec<i32>) -> i32 {
    // let v2 = v.split_off(v.len() / 2);
    // Clone トレイトを要求しないバージョン↓
    let v2: Vec<i32> = v.drain((v.len() / 2)..).collect();

    let t1 = thread::spawn(move || v.iter().sum::<i32>());
    let t2 = thread::spawn(move || v2.iter().sum::<i32>());

    let res1 = t1.join().unwrap();
    let res2 = t2.join().unwrap();

    res1 + res2
}
```

地味に `move` キーワードが登場していることにも注目。クロージャはデフォルトだと変数をなるべく「参照で」キャプチャしてこようとする(この参照は当然 `'static` ではない)ので、「実体を」キャプチャさせるようにするためにつけるキーワードです。7章8章ではクロージャに基本的に付与することになるでしょう。

余談、ちょっとカッコつけたDRYな感じの回答です。全部イテレータでおｋ！(行数増えてる気がするのは気の所為)

```rust:lib.rs
pub fn _sum(mut v: Vec<i32>) -> i32 {
    let v2 = v.drain((v.len() / 2)..).collect();

    vec![v, v2]
        .into_iter()
        .map(|v| thread::spawn(move || v.iter().sum::<i32>()))
        .collect::<Vec<_>>()
        .into_iter()
        .map(|handle| handle.join().unwrap())
        .sum()
}
```

どちらかと言えばイテレータにまつわる注意点ですが、一旦途中で `.collect::<Vec<_>>()` を挟んで、イテレータ内の全ての `thread::spawn` が実行された上で後の `.map(|handle| handle.join().unwrap())` が呼ばれるようにしなければなりません。

もし次のように書いたとしても、

```rust:lib.rs
use std::thread;

pub fn sum(mut v: Vec<i32>) -> i32 {
    let v2 = v.drain((v.len() / 2)..).collect();

    vec![v, v2]
        .into_iter()
        .map(|v| thread::spawn(move || v.iter().sum::<i32>()))
        .map(|handle| handle.join().unwrap())
        .sum()
}
```

これだとイテレータの特性より `thread::spawn` 後にすぐそのハンドルについて `handle.join()` が呼ばれてしまい、直列に実行されてしまいます。気をつけましょう！ <font color="#bbbbbb">こんな書き方する人自体が少数派かもですが...</font>

# [[07_threads/02_static](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/07_threads/02_static/src/lib.rs)] 'static ライフタイム

- [7.2. 'static lifetime](https://rust-exercises.com/07_threads/02_static)

問題はこちらです。

```rust:lib.rs
// TODO: Given a static slice of integers, split the slice into two halves and
//  sum each half in a separate thread.
//  Do not allocate any additional memory!
use std::thread;

pub fn sum(slice: &'static [i32]) -> i32 {
    todo!()
}
```

さっきの問題では引数として `Vec<i32>` 実体が渡されましたが、今回は「staticな実体への参照(これもまたstatic)」を引数として受け取り、同様に分割して足し合わせる並列処理を書く問題です。

今回は先ほどとは異なり **新しいメモリアロケーションが起きないようにせよ** という制約が加えられています。つまり、先程の回答の使い回しはしないでねということです。

<details><summary>テストを含めた全体</summary>

```rust:lib.rs
// TODO: Given a static slice of integers, split the slice into two halves and
//  sum each half in a separate thread.
//  Do not allocate any additional memory!
use std::thread;

pub fn sum(slice: &'static [i32]) -> i32 {
    todo!()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty() {
        static ARRAY: [i32; 0] = [];
        assert_eq!(sum(&ARRAY), 0);
    }

    #[test]
    fn one() {
        static ARRAY: [i32; 1] = [1];
        assert_eq!(sum(&ARRAY), 1);
    }

    #[test]
    fn five() {
        static ARRAY: [i32; 5] = [1, 2, 3, 4, 5];
        assert_eq!(sum(&ARRAY), 15);
    }

    #[test]
    fn nine() {
        static ARRAY: [i32; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
        assert_eq!(sum(&ARRAY), 45);
    }

    #[test]
    fn ten() {
        static ARRAY: [i32; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        assert_eq!(sum(&ARRAY), 55);
    }
}
```
</details>

## 解説

今度は逆に [`split_at`](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at) を使い、ヒープへのメモリアロケーションをさせない回答になります！

```rust:lib.rs
use std::thread;

pub fn sum(slice: &'static [i32]) -> i32 {
    let (s1, s2) = slice.split_at(slice.len() / 2);

    vec![s1, s2]
        .into_iter()
        .map(|s| thread::spawn(move || s.iter().sum::<i32>()))
        .collect::<Vec<_>>()
        .into_iter()
        .map(|handle| handle.join().unwrap())
        .sum()
}
```

`split_at` メソッド返り値の `(&[T], &[T])` のライフタイムは、引数 `&self` のライフタイムと同じになるので、今回は各スレッドに `'static` ライフタイムな値が行き、よってコンパイルエラーにはならず合計を計算できます！

そういえば `'static` ライフタイムでない値だとスレッドを跨げない理由の解説がまだでした。 ([次回](https://qiita.com/namn1125/items/34a0aacd4d40f9234aef)以降扱うスコープ付きスレッド等を除き) ずばり、 **スレッドそれ自体は「いつまで存在しているか」わからないため** です。いつ `join` が呼ばれるかはわからないので、スレッドがいつまで生きているかはわかりません！ `'static` ではないと次の図のような問題が起きる可能性があります。

```mermaid
sequenceDiagram
    Note over mainスレッド: v: Vec<i32>を作成
    activate mainスレッド
    mainスレッド->>別スレッド: &vとして借用
    activate 別スレッド
    Note right of 別スレッド: &vよ私と共に永遠にあれ...
    Note over mainスレッド: mainスレッド終了
    Note over mainスレッド: vを破棄(ぬるぽ)
    deactivate mainスレッド
    Note over 別スレッド: ｶﾞｯ!!
    deactivate 別スレッド
```

スレッド間でライフタイムを共有できるような術[^sube]を特に使わない場合、 `'static` ライフタイムな値しかクロージャに含められないという制約を置くことでとりあえずスレッド間の値の受け渡しを問題ないものとしているわけです。ライフタイムといい感じに付き合いながらスレッドを扱う術は次回以降の話題です！

[^sube]: 直接的にライフタイムを知らせ合うような仕組みはない(次回のスコープ付きスレッドはある意味これ...？)ですが、まぁ要は今回みたいな問題を回避できる他の方法、ぐらいの意味です。

「[`'static` ライフタイムは実体と参照で異なる意味を持つ](https://doc.rust-jp.rs/rust-by-example-ja/scope/lifetime/static_lifetime.html)」のがややこしい点ですが、これについて [Book](https://rust-exercises.com/07_threads/02_static) の解説が今回も秀逸なので最後の方を是非読んでみてほしいです。

> すなわち `'static` ライフタイムは次のように捉えることができます:
> 
> - 所有権ごと値をください！
> - プログラムが動いている間ずっと存在する参照(`&'static T`)をください！

実体と参照で意味が異なるとは言いますが、「ずっと存在『できる』のが `'static` ライフタイム」と捉えると上記2つの捉え方をカバーできて直感的なんじゃないかと思います。

「ずっとは存在できない」ケースは、言い換えると「(いつ無効になるかわからない)他のリソースへの依存を内包している」ということであり、 `'static` じゃない構造体(この後登場する [`MutexGuard<'_, T>`](https://doc.rust-lang.org/nightly/std/sync/struct.MutexGuard.html) とか)は大体このパターンです。参照それ自体なら「参照先はプログラムが動いている間ずっと存在」していれば存在できます。そして通常の構造体は「他のリソースを参照していない」か「依存している他のリソースもずっと存在」していれば存在できます。この辺を総括した名前として見ると、「 `'static` ライフタイム」という名称は結構しっくり来るのではないでしょうか？

```mermaid
sequenceDiagram
    participant Static as 静的データ
    participant Main as mainスレッド
    participant T1 as 別スレッド1
    participant T2 as 別スレッド2
    Note over Static: static HELLO: &'static str = "hello!"
    activate Static
    Static->>T2: HELLOを使用
    activate T2
    Note over Main: v: Vec<i32>を作成
    activate Main
    Main-->>T1: let v2 = v.clone();
    activate T1
    Note over Main: mainスレッド終了
    Note over Main: vを破棄
    deactivate Main
    Note over T1: v2は生き続けられる
    Note over T2: HELLOはいつまでも生きている
    deactivate T1
    deactivate T2
    deactivate Static
```

ところで、Rustが難しいから並列処理においてこんなライフタイムの話をしなければならないのでしょうか...？筆者は逆だと考えています。「並列処理自体が本来リソースのライフタイムにまで配慮するべき」なのです。他の言語でこの辺の話を曖昧にして処理を書くと「ｶﾞｯ!」「ｶﾞｯ!」「ｶﾞｯ!」の連続になることは想像に難くないです。 Rustは**ライフタイムというツールのお陰で並列処理で気をつけるべき点を簡単に説明できている**というわけです！故に、個人的には、 **並列処理や非同期処理の導入はRustの学習コストが他言語のそれと逆転する瞬間** なんじゃないかと思っています！逆に他言語話者はこの辺どうしてるんだ...

:::note
**[LazyCell](https://doc.rust-lang.org/std/cell/struct.LazyCell.html)・[LazyLock](https://doc.rust-lang.org/std/sync/struct.LazyLock.html)**

`static` 周りの話題としてタイムリーだったので取り上げたいと思います。 `LazyCell` ・ `LazyLock` は先日出た [Rust 1.80.0](https://blog.rust-lang.org/2024/07/25/Rust-1.80.0.html) にて安定化した機能で、共に「初めて参照された時にクロージャの中身を実行し、得られた値の参照を返す」という構造体です！

特に `LazyLock` の方は `static` を用いた変数宣言と共に用いられることで「最初から必要というわけではないけど、初めて参照された際に初期化されてくれるとありがたい」リソースや、「(コンパイル時には計算できないものの、)プログラム全体を通して有効な値とその参照を `static` に持ち続けたい」というようなケースで特に効率化に寄与します！(やりたいことの説明としては[この記事様](https://zenn.dev/frozenlib/articles/lazy_static_to_once_cell)がわかりやすいです)

一番わかりやすい例だと正規表現構造体 `Regex` の生成処理を一度で済ませる、等でしょうか。

```rust:Rust
use std::sync::LazyLock;
use regex::Regex;

static MAIL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"^[^\s@]+@[^\s@]+\.[^\s@\.]+$"#).unwrap()
});

fn main() {
    assert!(MAIL_REGEX.is_match("hoge+fuga@gmail.com"));
    assert!(!MAIL_REGEX.is_match("Invalid Mail Address."));
}
```

`LazyCell` と `LazyLock` の違いはスレッドセーフかどうかで、本章以降でこの辺も詳しく解説されますが、スレッドを跨いで使用する場合は `Sync` が実装された `LazyLock` の方を使います。また今回例を作ってわかりましたが、 `static` で使いたい場合スレッドを跨がさる[^hokkaido]ので同様の理由で `LazyLock` を使う必要があるみたいです！
:::

[^hokkaido]: 「〇〇さる」や「〇〇ささる」という東北・北海道弁の表現です。主語・主体の意志に反して勝手にそのような状態になってしまう時に使える便利な文法です(例: 「勝手に押ささる嫌な広告UIだ」)。広まれ～

スレッド関連初回ということで結構話しすぎました...では次の問題に行きましょう！

次の記事: [【28】 リーク・スコープ付きスレッド ～ライフタイムに技あり！～](https://qiita.com/namn1125/items/34a0aacd4d40f9234aef)