カテゴリ: Rust 更新日: 2026/03/30

Rustのstatic変数と定数を徹底解説!メモリ配置の仕組みとconstとの違い

Rustのstatic変数がメモリ上に配置される仕組み
Rustのstatic変数がメモリ上に配置される仕組み

先生と生徒の会話形式で理解しよう

生徒

「Rustでプログラム全体から使いたいデータがあるとき、どうすればいいんですか?変数をずっと保持しておきたいんです。」

先生

「それにはstatic変数やconst(定数)を使います。特にstatic変数は、プログラムが始まってから終わるまでずっと同じメモリ場所に存在し続ける特別な変数なんですよ。」

生徒

「ずっとメモリにあるなら、普通の変数(let)とは何が違うんですか?」

先生

「普通の変数はスコープを抜けると消えてしまいますが、静的変数はバイナリの中に直接埋め込まれるようなイメージで配置されます。ただ、Rustでは安全性(メモリ安全)のために、静的変数の扱いには少し厳しいルールがあるんです。一緒に詳しく見ていきましょう!」

1. Rustの静的変数(static)の基本概念

1. Rustの静的変数(static)の基本概念
1. Rustの静的変数(static)の基本概念

Rustにおけるstatic変数は、「静的ライフタイム」を持つ変数です。これは、プログラムの実行開始時にメモリが確保され、プログラムが終了するまでその領域が解放されないことを意味します。システムプログラミングにおいて、デバイスのグローバルな状態を保持したり、アプリケーション全体で共有する設定値を管理したりする際に非常に重要な役割を果たします。

通常のletキーワードで宣言されるローカル変数は「スタック」に積まれ、その関数やブロック(スコープ)が終了すると自動的に破棄されます。しかし、static変数はメモリの特定の領域(データセグメント)に配置されるため、どこからでもアクセス可能な「不変の居場所」を持つことになります。Rustはこの静的変数を厳格に管理することで、マルチスレッド環境でのデータ競合や、メモリの不正アクセスを防いでいます。

2. static変数とconst定数の決定的な違い

2. static変数とconst定数の決定的な違い
2. static変数とconst定数の決定的な違い

初心者の方が一番迷うのが、static(静的変数)とconst(定数)の使い分けです。どちらもプログラム全体で使える値を定義できますが、メモリ上の扱いは全く異なります。

constは、コンパイル時にその値が「インライン展開」されます。つまり、コードの中で定数名が書かれている場所すべてに、その値が直接書き込まれるイメージです。これに対し、staticはメモリ上の固定されたアドレスに「実体」が一つだけ存在します。そのため、大きなデータを扱う場合や、アドレスの一意性が重要な場合にはstaticを使用するのが適切です。

また、static変数は'staticライフタイムを持ちますが、定数はメモリ上の特定の場所を指し示すものではないため、根本的に性質が異なります。パフォーマンスの観点では、単純な数値や短い文字列ならconstを、大きな構造体や共有される状態ならstaticを選択するのが一般的です。


// 定数の定義(コンパイル時に展開される)
const MAX_POINTS: u32 = 100_000;

// 静的変数の定義(メモリ上の固定アドレスに配置される)
static APP_NAME: &str = "Rust Tutorial App";

fn main() {
    println!("名前: {}", APP_NAME);
    println!("最大スコア: {}", MAX_POINTS);
}

3. static変数がメモリ上に配置される仕組み

3. static変数がメモリ上に配置される仕組み
3. static変数がメモリ上に配置される仕組み

Rustのプログラムがコンパイルされて実行ファイル(バイナリ)になると、データはいくつかの領域に分かれて格納されます。static変数は、主に「データセグメント」と呼ばれる領域に配置されます。具体的には、初期化済みのデータが格納される領域です。

プログラムがメモリにロードされると、OSはこのデータセグメントをメモリ上に展開します。static変数はこの時点でアドレスが確定し、以後、プログラムが終了するまでそのアドレスが変わることはありません。これが「静的(static)」と呼ばれる理由です。CPUはこの固定されたアドレスを直接参照するため、ポインタを介した間接的なアクセスに比べて高速に動作する場合もあります。

対照的に、ローカル変数は実行時にスタックポインタが移動することで動的に確保されます。静的変数はコンパイル時点で「どこにどれだけのサイズで存在するか」がすべて決まっているため、メモリ管理のオーバーヘッドが極めて小さいのが特徴です。

4. 静的変数の初期化と制限事項

4. 静的変数の初期化と制限事項
4. 静的変数の初期化と制限事項

Rustでは、static変数を宣言する際に必ず型を指定し、定数式で初期化しなければなりません。関数呼び出しの結果を直接static変数に代入することはできません。これは、プログラムの実行が始まる「前」に値が決まっていないと、メモリ上に配置できないからです。

例えば、実行時のユーザー入力によって決まる値をstaticに入れることは不可能です。もし実行時に一度だけ初期化したい(遅延初期化)場合は、標準ライブラリのstd::sync::OnceLockや、外部クレートのonce_cellなどを使用する必要があります。これにより、安全に「実行時に決まる静的な値」を扱うことができます。


use std::sync::OnceLock;

// 実行時に初期化したい静的変数
static CONFIG_PATH: OnceLock<String> = OnceLock::new();

fn main() {
    // 最初のアクセス時に値をセットする
    let path = CONFIG_PATH.get_or_init(|| {
        "user/local/config.toml".to_string()
    });
    
    println!("設定ファイルのパス: {}", path);
}

5. ミュータブルな静的変数の危険性とunsafe

5. ミュータブルな静的変数の危険性とunsafe
5. ミュータブルな静的変数の危険性とunsafe

通常、static変数は不変(イミュータブル)ですが、static mutと宣言することで値を変更可能にすることもできます。しかし、これはRustにおいて非常に危険な操作と見なされます。なぜなら、複数のスレッドから同時にアクセスされた場合、データ競合(Data Race)が発生する可能性があるからです。

そのため、static mut変数の読み書きには必ずunsafeブロックが必要になります。Rustのコンパイラは、この変数がいつ誰に書き換えられるかを保証できないため、プログラマが責任を持って安全性を確保しなければなりません。現代のRust開発では、グローバルな状態を管理したい場合はstatic mutを避け、Atomic型やMutexRwLockを組み合わせて安全に共有する方法が推奨されています。


// 変更可能な静的変数(非推奨だが可能)
static mut COUNTER: u32 = 0;

fn main() {
    // static mutへのアクセスは常にunsafe
    unsafe {
        COUNTER += 1;
        println!("カウンターの値: {}", COUNTER);
    }
}

6. 安全にグローバルな状態を管理する方法

6. 安全にグローバルな状態を管理する方法
6. 安全にグローバルな状態を管理する方法

前述の通り、static mutはリスクが高いため、実務ではスレッド安全な型を使用します。例えば、整数値をグローバルにカウントしたい場合は、std::sync::atomic::AtomicU32などを使います。これらはCPUレベルで原子性を保証するため、unsafeを使わずに安全に複数の場所から値を更新できます。

より複雑なデータ構造(例えばハッシュマップやベクタ)を静的に保持したい場合は、Mutex(相互排除)を使用して、一度に一つのスレッドしかデータに触れないようにロックをかけます。これにより、Rustのメモリ安全性を維持したまま、プログラム全体で一貫したデータを共有することが可能になります。初心者の方は、まずは「安全な静的変数」の作り方を学ぶことが、Rustらしいプログラミングへの近道です。


use std::sync::atomic::{AtomicU32, Ordering};

// アトミック型を使用した安全な共有変数
static GLOBAL_ID_COUNTER: AtomicU32 = AtomicU32::new(1000);

fn main() {
    // fetch_addはスレッド安全に値を加算する
    let old_id = GLOBAL_ID_COUNTER.fetch_add(1, Ordering::SeqCst);
    let new_id = GLOBAL_ID_COUNTER.load(Ordering::SeqCst);
    
    println!("以前のID: {}, 現在のID: {}", old_id, new_id);
}

7. 静的ライフタイムと参照の深い関係

7. 静的ライフタイムと参照の深い関係
7. 静的ライフタイムと参照の深い関係

Rustを学んでいると頻繁に遭遇する'staticという記法は、この静的変数と深い関わりがあります。'staticライフタイムを持つ参照は、プログラムが動いている間ずっと有効であることを保証します。文字列リテラル(例: "hello")が型として&'static strになるのは、その文字列データ自体がバイナリの読み取り専用データセグメントに静的に配置されているからです。

静的変数を参照する場合、その参照も必然的に'staticになります。これは、参照先のデータが消える心配が絶対にないという強力な保証をコンパイラに与えます。この仕組みがあるおかげで、Rustはポインタが「どこを指しているか」だけでなく「いつまで有効か」を完璧に追跡でき、安全なプログラムを実現しているのです。メモリ配置の仕組みを理解することは、ライフタイムの概念を理解するための大きな助けになります。

8. 静的変数の活用シーンとベストプラクティス

8. 静的変数の活用シーンとベストプラクティス
8. 静的変数の活用シーンとベストプラクティス

最後に、どのような場面でstaticを使うべきかを整理しましょう。主な用途は、組み込み開発でのレジスタ操作、ログ出力用のグローバルロガー、アプリケーションのバージョン情報や固定の変換テーブル(巨大な配列)などです。これらは実行中に内容が変わることがない、あるいは厳密な同期が必要な共有リソースです。

ベストプラクティスとしては、可能な限りconstを優先し、どうしても実体としてのメモリアドレスが必要な場合や、状態を共有する必要がある場合のみstaticを選択することです。また、静的変数に格納するデータは、不必要なメモリ消費を避けるために最小限に留めるのが賢明です。Rustの所有権システムは非常に強力ですが、この静的変数を正しく使いこなすことで、さらに高度なシステム設計が可能になります。メモリの仕組みを意識しながら、安全で効率的なコードを書いていきましょう。

カテゴリの一覧へ
新着記事
New1
C++
C++の関数の作り方を完全ガイド!初心者でもわかる基本構文と定義方法
New2
Rust
Rustのビット演算子とビット操作を徹底解説!低レイヤ開発への第一歩
New3
C言語
C言語の配列名はポインタ?暗黙の変換を初心者向けにわかりやすく解説
New4
C++
C++が今でも現役で使われる理由を徹底解説!長年愛されるプログラミング言語の魅力
人気記事
No.1
Java&Spring記事人気No1
C言語
C言語を学ぶ初心者におすすめの環境構築手順【2025年版】
No.2
Java&Spring記事人気No2
C言語
C言語のソースコードとヘッダファイルの役割とは?初心者向けにわかりやすく解説!
No.3
Java&Spring記事人気No3
C++
MinGWとMSYS2でWindowsにC++環境を構築する方法を徹底解説!初心者でもできるセットアップガイド
No.4
Java&Spring記事人気No4
C言語
Visual Studio CodeでC言語を実行する方法【拡張機能の設定と実行手順】
No.5
Java&Spring記事人気No5
C言語
LinuxでC言語開発環境を構築する方法【GCCとMakefileの基本】
No.6
Java&Spring記事人気No6
C言語
C言語開発でよく使われるエディタとIDEランキング【初心者向け完全ガイド】
No.7
Java&Spring記事人気No7
C++
CMakeの基本構文とCMakeLists.txtを初心者向けに解説
No.8
Java&Spring記事人気No8
C言語
C言語をオンラインで実行できる便利なコンパイラサービスまとめ【初心者向け】