Chapter 03

【3日目】型と変数

okkn
okkn
2021.12.01に更新

プリミティブ型について

変数について見ていきたいところですが、変数に指定できるプリミティブ型を先に見てみましょう。
※汎用的な型の表記として "T, U" を使用します。

以下の型はRustにおけるプリミティブなスカラ型になります。

型名 備考
unit ユニット型 誤解を恐れずに言うと void
bool 論理値型 true, false
i8 8bit符号付き整数型
u8 8bit符号なし整数型
i16 16bit符号付き整数型
u16 16bit符号なし整数型
i32 32bit符号付き整数型 型推論でのデフォルト
u32 32bit符号なし整数型
i64 64bit符号付き整数型
u64 64bit符号なし整数型
i128 128bit符号付き整数型
u128 128bit符号なし整数型
isize 符号付き整数型 CPUのメモリアドレスのビット幅に依存
usize 符号なし整数型 CPUのメモリアドレスのビット幅に依存
f32 32bit浮動小数点型
f64 64bit浮動小数点型 型推論でのデフォルト
char 文字型
&T, &mut T 参照型
*const T, *mut T 生ポインタ型 unsafe
fn(T) -> U 関数ポインタ型

以下の型はRustにおけるプリミティブな複合型になります。

型名 備考
(T, U, ...) タプル型 カンマで区切った要素の組を表す
[T; size] 配列型 sizeには数字を入れ、コンパイル時の固定長
&[T], &mut[T], Box<[T]> スライス型 長さは実行時決定
&str 文字列スライス型

変数について

Rustでは変数周りに特徴的な仕組みが多く使われています。
変数の定義自体について詳しく見ていきたいと思います。

// 基本の構文
let 変数[: T] [= 初期化式];

let文 を用いることで変数の定義が行なえます。
Rustでは変数と値を結びつけることを 「束縛する」 と言います。
よく他言語では代入と言ったりしますが、Rustではそうは言いません。

Rustでは初期化式は省略しても変数の定義は可能ですが、できるだけ避けるべきです。
なぜなら、値のない状態の変数を使用することはできないからです。
コンパイル時にエラーになります。
忘れないためにも、何らかの理由で初期化できない場合を除いて、初期化は変数の定義時に行なっておくほうが良いでしょう。

ミュータビリティについて

let文 で定義した変数はデフォルトでイミュータブル(変更不可)になります。
Rustは変数の値の変更についてかなり厳格である証拠だと思います。
他言語では逆にデフォルトでミュータブル(変更可能)なものが多いですね。
ミュータブルな変数を定義するには以下のように書きます。

// 基本の構文
let mut 変数[: T] [= 初期化式];

これで他の言語と同様にコード内で値の変更を行うことができます。
とは言え、Rust的な思想として必要な変数以外はミュータブルにすべきではないと思います。

スコープについて

変数のスコープについては { } (ブロック)内でのみ有効です。

fn scope_example() {
    let x = 10;

    if x == 10 {
        let y = 20;
        println!("{}", y);
    }

    {
        let z = 30;

        // println!("{}", y); <= エラーになる
        println!("{}", z);
    }

    println!("{}", x);
    // println!("{}", y); <= エラーになる
    // println!("{}", z); <= エラーになる
}

コメントで「エラーになる」とある部分は全てスコープ外のため、変数の値がない状態になっているので参照できません。

シャドウイングについて

変数は同じ名前で複数作成することができます。

fn shadowing_example() {
    let x = 10;        // xを定義
    let x = 20;        // 新しいxを定義
    let x = "hoge"     // 新しい変数を定義して値に束縛するので、変数の型が変わっても問題ない

    println!("{}", x); // xは"hoge"

    {
        let x = 30;        // ブロック内のみで有効なx
        println!("{}", x); // xは30
    }

    println!("{}", x); // xは"hoge"
}

一時利用の変数に関して、変な名前をつけなくて良いという利点があります。
ただし、ブロック内で変数名が被ってしまうと再定義されて予期しないことになるので、そこはしっかりと意識しなければなりません。
シャドウイングは一度定義した変数の束縛先を変更するものではなく、新しい変数の定義なので、変数のミュータビリティとは別の話です。

定数とスタティック変数

const文 を用いることで定数も定義できます。
定数は一度値が定義されると、値の変更はできません。
また、シャドウイングのようなこともできません。

// 基本の構文
const 名前: T = 定数式;

static文 を用いることで、グローバルスコープで利用可能なスタティック変数の定義ができます。

// 基本の構文
static [mut] 名前: T = 定義式;

定数はコンパイル時に一度だけ計算され値が埋め込まれます。
他方、スタティック変数は参照のたびに計算が行われます。
そのため、ミュータブルにすることも可能です。
ミュータブルなスタティック変数の読み書きには unsafe な環境下で行う必要があります。

const TEST: i32 = 10;
static mut GLOBAL_FLAG: bool = false;

fn main() {
    println!("{}", TEST);  // 10

    unsafe {
        println!("{}", GLOBAL_FLAG);  // false
        GLOBAL_FLAG = true;
        println!("{}", GLOBAL_FLAG);  // true
   }
}

unsafe については別途説明予定です。

最後に

変数の定義関連について見てみました。
基礎的な部分は忘れがちですが、しっかりと覚えておくべきです。

参照文献

https://doc.rust-jp.rs/book-ja/ch03-02-data-types.html
https://doc.rust-jp.rs/book-ja/ch03-01-variables-and-mutability.html