🗿

Rustのlet/const/staticの違い

2024/12/06に公開

Rustでコードを書いているとき,文字列リテラルや数値を変数として扱うか,定数として扱うか悩みました.
Rustではlet変数の値は通常不変なので,定数のように扱うこともできます.一方でRustにはconst定数やstatic変数といったものも存在します.
それぞれどのような違いがあり,どういった用途で使い分けるのが良いのか,自分なりにまとめてみました.

let変数

宣言した変数に値を束縛します.通常の束縛では値の所有権を保持します.
値が参照のときは,その値の借用を行います.この場合,値の所有権は保持しません.
つまり,letで所有・借用する値の実体は,必ずプログラム中で1つしか存在しません.

// String型のインスタンスをstr1に束縛
let str1 = String::from("Hello");
// str1の持つ"Hello"の所有権をstr2に渡す
let str2 = str1;
println!("{}", str2);
// str1にアクセスすると所有権がないのでエラー
// println!("{}", str1);
// str2の参照をstr3に渡す(借用)
let str3 = &str2;
// "Hello"の所有権はstr2のままなのでエラーにならない
println!("{} {}", str2, str3);

let変数の値は実行時に評価されます.let変数を使用した式は実行時に計算されます.

束縛される値は通常不変ですが,mutキーワードをつけることで可変となります.ただし値がすでにほかの変数で参照されている場合は,すべての参照が外れるまでmutで束縛できません.

// 可変なstr4にstr2に束縛された値の所有権を渡す
let mut str4 = str2;
// str5でstr4を不変参照していると,str6で借用できない
// let str5 = &str4;
// str4への可変参照をstr6に借用する
let str6 = & mut str4;
str6.push_str("!");
println!("{}", str4);

変数は関数スコープまたはブロックスコープで宣言できます.グローバルスコープでは宣言できません.

スコープ内で同じ名前の変数を宣言しても,エラーになりません.これをシャドーイングといい,変数の再宣言以降は新しい宣言を使って式が評価されます.シャドーイングが有効な変数のスコープから抜けると,再宣言以前の変数が使用されます.

let hoge = "hoge";
{
    let hoge = "fuga";
    // "fuga"が表示される
    println!("{}", hoge);
}
// "hoge"が表示される
println!("{}", hoge);

モジュール外に変数を公開することはできません.ただし,変数の値を返す関数をモジュール外に公開することはできます.

const定数

定数として値を宣言します.
定数には所有権や借用といった概念はなく,const宣言された値はコンパイル時にコードにインライン展開されます.

const HOGE: &str = "hoge";
// コンパイル時に println!("{}", "hoge"); となる
println!("{}", HOGE);
}

const定数の値はコンパイル時に評価されます.const定数またはconst関数だけで構成される式は,コンパイル時に計算されます.

const fn plus_one(n: i32) -> i32 {
    n + 1
}

const N: i32 = 123;
const M: i32 = plus_one(N);
// コンパイル時に println!("{}", 124); となる
println!("{}", M);

定数なので値は不変です.

const定数はグローバルスコープを含む任意のスコープで宣言できます.また宣言した定数はモジュール外に公開することができます.

pub const EXPOSED_VALUE: &str = "Exposed";

static変数

static変数に束縛された値はインライン展開されず,プログラム中の1つのメモリ空間に割り当てられます.
これは複数のスレッドから見て,必ず一つの同一なメモリ空間を指します.

static hoge: &str = "hoge";

// 複数のスレッドからスレッドセーフにアクセスできる
let s = &hoge;
println!("{}", s);

static変数の値は実行時に評価されます.

static変数はmutキーワードを使って可変にすることもできます.ただし可変にするとスレッドセーフではなくなるため,unsafeブロックで囲まないと読み書きができません.

static mut UNSAFE_VALUE: i32 = 1;

// スレッドセーフでないのでunsafeブロック以外では読み書きできない
unsafe {
    UNSAFE_VALUE += 1;
    println!("{}", UNSAFE_VALUE);
}

static変数はグローバルスコープを含む任意のスコープで宣言できます.モジュール外に公開することも可能です.

私的使い分け

基本的にはlet変数を使用し,用途によってconst定数やstatic変数を使おうと思います.

  • const定数
    • 値をモジュール外に公開したいとき.
    • プログラムやモジュール内で共有する値をグローバルスコープで宣言したいとき.
    • コンパイル時に値を計算したいとき.
    • 値をインライン展開したいとき.
  • static変数
    • シングルトンのようにプログラム中で1つしか存在しないグローバルなオブジェクトを定義したいとき.
  • let変数
    • それ以外のとき.

Discussion