diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 6d03c91..2049b23 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -120,7 +120,7 @@ - [Unsafe `extern` blocks](rust-2024/unsafe-extern.md) - [Unsafe attributes](rust-2024/unsafe-attributes.md) - [`unsafe_op_in_unsafe_fn` warning](rust-2024/unsafe-op-in-unsafe-fn.md) - - [Disallow references to `static mut`](rust-2024/static-mut-references.md) + - [static mut への参照の禁止](rust-2024/static-mut-references.md) - [Never type fallback change](rust-2024/never-type-fallback.md) - [Macro fragment specifiers](rust-2024/macro-fragment-specifiers.md) - [Missing macro fragment specifiers](rust-2024/missing-macro-fragment-specifiers.md) diff --git a/src/rust-2024/static-mut-references.md b/src/rust-2024/static-mut-references.md index f6553fd..2bfed55 100644 --- a/src/rust-2024/static-mut-references.md +++ b/src/rust-2024/static-mut-references.md @@ -1,17 +1,42 @@ -> **Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、[原文(英語版)](https://doc.rust-lang.org/nightly/edition-guide/introduction.html)をご参照ください。** - + + +# static mut への参照の禁止 + +## 概要 + + + +- [`static_mut_refs`] のリントレベルはデフォルトで `deny`(必ずエラー)になりました。 + このリントは、`static mut` への共有参照・可変参照を検出します。 + +[`static_mut_refs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#static-mut-refs + + + +## 詳細 + + +[`static_mut_refs`] リントは、[`static mut`] への参照を検出します。 +2024 エディションではこのリントはデフォルトで `deny`(不許可=必ずエラー)となります。 +これは、[`static mut`] への参照は基本的に作るべきでないと強調するためです。 ```rust @@ -22,12 +47,25 @@ unsafe { let y = &X; // ERROR: shared reference to mutable static let ref x = X; // ERROR: shared reference to mutable static let (x, y) = (&X, &Y); // ERROR: shared reference to mutable static + // (訳) エラー: 可変スタティック変数への共有参照 } ``` + + +Rust の可変性に関するエイリアシングルール[^1]を破るような参照を生成するのは、**たとえその参照が実際に読み書きされないとしても**、今も昔も問答無用で[未定義動作]です。 +その上、このルールは**コード全体で**守られてなくてはならず、特に再入可能性やマルチスレッドが関わると困難なタスクです。 +[^1]: 一つの値に対して、ただ一つの可変参照か、複数の共有参照の、たかだか一方しか存在することができないというルールのこと。 + + + +注意すべきなのは、表向きは `&` が使われていなくても暗黙に参照が生成される場面がある点です。 +たとえば、以下のような使い方もリントに検知されます。 ```rust @@ -36,36 +74,95 @@ static mut NUMS: &[u8; 3] = &[0, 1, 2]; unsafe { println!("{NUMS:?}"); // ERROR: shared reference to mutable static let n = NUMS.len(); // ERROR: shared reference to mutable static + // (訳) エラー: 可変スタティック変数への共有参照 } ``` + +## 代替案 + + + +可能な限り、(可変性エイリアシングルールの検証がはるかに楽となるように)**局所的に正当化された抽象化**にもとづく**内部可変性**を提供する型の、`static` で**不変な**変数を使うことが**強く推奨**されます。 + +局所的正当化による抽象化が不可能で、どうしても `static` 変数へのグローバルな正当化に基づくアクセスが必要なときは、[`&raw const`・`&raw mut` 演算子][raw]を用いるなどして生ポインタを使う必要があります。 +直接参照を取る代わりに先に生ポインタを作っておけば、そのポインタへのアクセス(に必要な安全性要件)は `unsafe` コードを書く人にとっても慣れたものですし、より局所的なコードに限定(先送り)できます。 + + + +[未定義動作]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html +[`static mut`]: https://doc.rust-lang.org/reference/items/static-items.html#mutable-statics +[`addr_of_mut!`]: https://docs.rust-lang.org/core/ptr/macro.addr_of_mut.html +[raw]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#raw-borrow-operators + +なお、以下に示すのはあくまで例示で、厳密な実装というわけではありません。 +そのまま丸写ししないでください。 +コードによって事情も異なり、用途に合わせて書き換えるべき点もあるでしょう。 +ここでは、様々な問題に対処するための選択肢を示すために、何個かの例を列挙しています。 + + + +[未定義動作]のリファレンスや [Rust 裏本]、標準ライブラリで提供される型を使用する場合はそのドキュメントなどをよくお読みください。 +疑問点は[ユーザーズフォーラム]などの Rust フォーラムで質問するとよいでしょう。 + + +[未定義動作]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html +[Rust 裏本]: https://doc.rust-jp.rs/rust-nomicon-ja/index.html +[ユーザーズフォーラム]: https://users.rust-lang.org/ + +> **訳注**: 日本語話者向けの Rust コミュニティとして [Zulip](https://rust-lang-jp.zulipchat.com/) がありますので、併せてご活用ください。 + +### グローバル変数を使わない + + + +ご存知かもしれませんが、グローバルな状態を書き換えなくて済むならそれが一番です。 +もちろん場合によっては、特に可変参照をたくさんの関数に引き回す必要のあるときは、若干の困難や面倒の種です。 + + +### アトミック型 + +[アトミック型][atomics]を使うと、`static` 文脈で(`mut` なしに)使える整数・ポインタ・論理型を定義できます。 + + + +```rust,edition2024 +# use std::sync::atomic::Ordering; +# use std::sync::atomic::AtomicU64; + +// 以下の置き換えが可能 +// static mut COUNTER: u64 = 0; +static COUNTER: AtomicU64 = AtomicU64::new(0); + +fn main() { + // コードに応じて適切な `Ordering` を指定する + COUNTER.fetch_add(1, Ordering::Relaxed); +} +``` + + +[atomics]: https://doc.rust-lang.org/std/sync/atomic/index.html + +### Mutex と RwLock + + + +アトミック型より複雑な型を使いたい場合は、グローバル変数への適切なアクセスの確保のために [`Mutex`] や [`RwLock`] が使えます。 + +```rust,edition2024 +# use std::sync::Mutex; +# use std::collections::VecDeque; + +// 以下の置き換えが可能 +// static mut QUEUE: VecDeque = VecDeque::new(); +static QUEUE: Mutex> = Mutex::new(VecDeque::new()); + +fn main() { + QUEUE.lock().unwrap().push_back(String::from("abc")); + let first = QUEUE.lock().unwrap().pop_front(); +} +``` + + + +[`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +[`RwLock`]: https://doc.rust-lang.org/std/sync/struct.RwLock.html + + +### OnceLock と LazyLock + +`const` 文脈で実行できない一度限りの初期化をするために `static mut` を使っている場合、[`OnceLock`] や [`LazyLock`] に置き換えられます。 + + +```rust,edition2024 +# use std::sync::LazyLock; +# +# struct GlobalState; +# +# impl GlobalState { +# fn new() -> GlobalState { +# GlobalState +# } +# fn example(&self) {} +# } + +// 以下のように未初期化な値で埋めておく代わりに、以下のように書ける +// static mut STATE: Option = None; +static STATE: LazyLock = LazyLock::new(|| { + GlobalState::new() +}); + +fn main() { + STATE.example(); +} +``` + + + +[`OnceLock`] と [`LazyLock`] は似ていますが、[`OnceLock`] は初期化パラメータを渡す必要がある場合に使え、特に初期化箇所が単一のときや(`main` など)、アクセス時にいつでも初期化パラメータを持っているときなどに便利です。 ```rust,edition2024 # use std::sync::OnceLock; @@ -165,13 +347,29 @@ fn main() { } ``` + + +[`OnceLock`]: https://doc.rust-lang.org/std/sync/struct.OnceLock.html +[`LazyLock`]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html + +### `no_std` な一度限りの初期化 + + + +以下の例はグローバル変数を一度だけ初期化するという点で [`OnceLock`] と似ていますが、`std` が不要なので `no_std` 文脈で便利です。 +ターゲット環境がアトミック型をサポートするなら、グローバル変数の初期化が完了しているかどうかをアトミック型で管理できます。 +例えば、以下のように書けるでしょう。 + +```rust,edition2024 +# use core::sync::atomic::AtomicUsize; +# use core::sync::atomic::Ordering; +# +# struct Args { +# verbose: bool, +# } +# fn parse_arguments() -> Args { +# Args { verbose: true } +# } +# +# struct GlobalState { +# verbose: bool, +# } +# +# impl GlobalState { +# const fn default() -> GlobalState { +# GlobalState { verbose: false } +# } +# fn new(verbose: bool) -> GlobalState { +# GlobalState { verbose } +# } +# fn example(&self) {} +# } + +const UNINITIALIZED: usize = 0; +const INITIALIZING: usize = 1; +const INITIALIZED: usize = 2; + +static STATE_INITIALIZED: AtomicUsize = AtomicUsize::new(UNINITIALIZED); +static mut STATE: GlobalState = GlobalState::default(); + +fn set_global_state(state: GlobalState) { + if STATE_INITIALIZED + .compare_exchange( + UNINITIALIZED, + INITIALIZING, + Ordering::SeqCst, + Ordering::SeqCst, + ) + .is_ok() + { + // SAFETY: STATE への読み書きは INITIALIZED で保護されている + unsafe { + STATE = state; + } + STATE_INITIALIZED.store(INITIALIZED, Ordering::SeqCst); + } else { + panic!("already initialized, or concurrent initialization"); + } +} + +fn get_state() -> &'static GlobalState { + if STATE_INITIALIZED.load(Ordering::Acquire) != INITIALIZED { + panic!("not initialized"); + } else { + // SAFETY: state が初期化されると、グローバルなアクセスは不可能になる + unsafe { &*&raw const STATE } + } +} + +fn main() { + let args = parse_arguments(); + let state = GlobalState::new(args.verbose); + set_global_state(state); + // ... + let state = get_state(); + state.example(); +} +``` + + +この例では、スタティック変数は最初何らかのデフォルト値(上記の例では const な `default` コンストラクタ)で埋められています。 +それが不可能なときは、他のデフォルト値の埋め方として、[`MaybeUninit`] を使ったり、動的トレイトディスパッチを使って、初期値はそのトレイトを実装するダミー型にしておいたりするとよいでしょう。 + + + +[`static-cell`] など、一度限りの初期化機能を提供するようなサードパーティークレートを使うこともできます(このクレートは [`portable-atomic`] を利用しており、アトミック型が使えないターゲット環境でも利用可能です)。 + + +[`MaybeUninit`]: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html +[`static-cell`]: https://crates.io/crates/static_cell +[`portable-atomic`]: https://crates.io/crates/portable-atomic + +### 生ポインタ + + + +`static mut` を使い続けつつ、参照の生成を避ける方法もあります。 +例えば、C のライブラリに[生ポインタ]を渡す必要があるだけだったら、参照を経由する代わりに以下のように[生参照演算子]を使うとよいでしょう。 + + +```rust,edition2024,no_run +# #[repr(C)] +# struct GlobalState { +# value: i32 +# } +# +# impl GlobalState { +# const fn new() -> GlobalState { +# GlobalState { value: 0 } +# } +# } + +static mut STATE: GlobalState = GlobalState::new(); + +unsafe extern "C" { + fn example_ffi(state: *mut GlobalState); +} + +fn main() { + unsafe { + // 以下の書き換えが可能 + // example_ffi(&mut STATE as *mut GlobalState); + example_ffi(&raw mut STATE); + } +} +``` + +ただし、可変ポインタのエイリアシング規則は変わらず遵守する必要があります。 +内部や外部での同期や、マルチスレッド・割り込み・再入可能性などの観点からの正常な利用を保証してください。 + + + +[生参照演算子]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#raw-borrow-operators +[生ポインタ]: https://doc.rust-lang.org/reference/types/pointer.html#raw-pointers-const-and-mut + +### `Sync` な `UnsafeCell` + + + +[`UnsafeCell`] は `Sync` を impl しないので、`static` 文脈で使えません。 +`static` 文脈で内部可変性が利用できるように、[`UnsafeCell`] の独自の `Sync` なラッパーを作ることができます。 +特に、可変ポインタが要求する安全性不変条件を守るための機構(外部ロックなど)がある場合には、この方法が有効です。 + +なおこの例は、[生ポインタ](#生ポインタ)の例とほぼ同等です。 +このようなラッパーを使うことで、型の使用方法を強調して注意すべき安全性要件に着目させることができますが、他の点は大方同じです。 + + + +```rust,edition2024 +# use std::cell::UnsafeCell; +# +# fn with_interrupts_disabled(f: T) { +# // 本当ならここで割り込みを無効化する +# f(); +# } +# +# #[repr(C)] +# struct GlobalState { +# value: i32, +# } +# +# impl GlobalState { +# const fn new() -> GlobalState { +# GlobalState { value: 0 } +# } +# } +#[repr(transparent)] +pub struct SyncUnsafeCell(UnsafeCell); + +unsafe impl Sync for SyncUnsafeCell {} + +static STATE: SyncUnsafeCell = SyncUnsafeCell(UnsafeCell::new(GlobalState::new())); + +fn set_value(value: i32) { + // (訳注) with_interrupts_disabled は、 + // 割り込みを無効化した状態で処理を実行する関数という想定 + with_interrupts_disabled(|| { + let state = STATE.0.get(); + unsafe { + // SAFETY: この値は割り込みハンドラでしか読まれず、 + // 今は割り込みが無効化されており、かつこれは一つのスレッドからしか実行されない + (*state).value = value; + } + }); +} +``` + + + +標準ライブラリには [`UnsafeCell`] の変種として [`SyncUnsafeCell`] がありますが、nightly 限定です(安定化されていません)。 +上記のサンプルコードは `SyncUnsafeCell` の超簡略版ですが、使用方法はほぼ同じです。 +`SyncUnsafeCell` ではより厳密な保護策がとられているので、実装を読むと詳細な設計の参考になるでしょう。 + +本例では仮想的な `with_interrupts_disabled` 関数を使っていますが、組み込み環境では実際にこの手のものがあるでしょう。 +例えば、[`critical-section`] クレートを使うと、組み込み環境で似たような機能が使えます。 + + + +[`critical-section`]: https://crates.io/crates/critical-section +[`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html +[`SyncUnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.SyncUnsafeCell.html + +### 安全な参照 + + + +`static mut` の参照を作っても安全な場面も存在するにはします。 +[`static_mut_refs`] リントが伝えたいのは正しい使用が非常に困難だということで、不可能ではないのです。 +エイリアシング規則が遵守できていると保証できるなら、参照を作っても問題ないでしょう。 +例えば、スタティック変数のスコープが十分小さかったり(小さいモジュールや関数など)、内部や外部に同期機構があったり、割り込みハンドラや再入可能性、パニック時の安全性、ドロップハンドラなども十分に考慮できる場合です。 + + +この場合、2 つの方法があります。 +[`static_mut_refs`] リントを(できるだけ狭い範囲で)allow とする(エラーが出ないようにする)するか、生ポインタを `&mut *&raw mut MY_STATIC` のように参照に変換するかです。 + + +#### 参照を短命にする + +`static mut` への参照を作る必要があるときは、参照が存在するスコープをできるだけ小さくすることが肝要です。 +作った参照を後で使うために大事に取っておいたり、コード中で長い間捨てずに保持し続けたりしないでください。 +参照を短命にしておくことで、その間だけ排他的アクセスが保持されていることが検証しやすくなります。 +基本的にポインタを使っておき、どうしても必要なときだけ参照に変換するとよいでしょう。 + + + +## 移行 + + +`static mut` への参照に関する自動移行はありません。 +未定義動作を避けるために、[代替案](#代替案)の節で示したような方法での修正が必要です。