列挙型

enum キーワードを使うと、いくつかの異なる バリアントを持つ型を作成できます。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
enum Direction {
    Left,
    Right,
}

#[derive(Debug)]
enum PlayerMove {
    Pass,                        // 単純なバリアント
    Run(Direction),              // タプルバリアント
    Teleport { x: u32, y: u32 }, // 構造体バリアント
}

fn main() {
    let dir = Direction::Left;
    let player_move: PlayerMove = PlayerMove::Run(dir);
    println!("On this turn: {player_move:?}");
}

ポイント:

  • 列挙型を使うと、一連の値を 1 つの型の下にまとめられます。
  • Direction はバリアントを持つ型です。Direction の値には 2 つあります: Direction::LeftDirection::Right です。
  • PlayerMove は 3 つのバリアントを持つ型です。Rust はペイロードに加えて、 実行時に PlayerMove の値にどのバリアントが入っているかを判別できるよう、 判別子も格納します。
  • ここで、構造体と列挙型を比較してみるとよいでしょう:
    • どちらでも、フィールドのない単純な形(ユニット構造体)や、 異なる型のフィールドを持つ形(バリアントのペイロード)を持てます。
    • 列挙型の異なるバリアントを別々の構造体として実装することもできますが、 その場合、それらをすべて列挙型の中で定義した場合のように同じ型にはなりません。
  • Rust は判別子を格納するために必要最小限の領域を使います。
    • 必要であれば、必要最小のサイズの整数を格納します

    • 許可されたバリアント値がすべてのビットパターンを網羅していない場合、 Rust は無効なビットパターンを使って判別子を符号化します(「ニッチ最適化」)。 たとえば、Option<&u8> は整数へのポインタ、または None バリアントを表す NULL を格納します。

    • 必要に応じて判別子を制御できます(たとえば、C との互換性のため):

      // Copyright 2023 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      #[repr(u32)]
      enum Bar {
          A, // 0
          B = 10000,
          C, // 10001
      }
      
      fn main() {
          println!("A: {}", Bar::A as u32);
          println!("B: {}", Bar::B as u32);
          println!("C: {}", Bar::C as u32);
      }

      repr がない場合、10001 は 2 バイトに収まるため、判別子の型は 2 バイトになります。

さらに学ぶ

Rust には、列挙型が占有する領域を小さくするために適用できる最適化が いくつかあります。

  • ヌルポインタ最適化: 一部の型について、 Rust は size_of::<T>()size_of::<Option<T>>() と等しいことを保証します。

    実際にビット単位の表現がどのように見える 可能性がある かを示したい場合の コード例です。この表現についてコンパイラはいかなる保証も提供しないため、 これは完全に unsafe です。

    // Copyright 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    use std::mem::transmute;
    
    macro_rules! dbg_bits {
        ($e:expr, $bit_type:ty) => {
            println!("- {}: {:#x}", stringify!($e), transmute::<_, $bit_type>($e));
        };
    }
    
    fn main() {
        unsafe {
            println!("bool:");
            dbg_bits!(false, u8);
            dbg_bits!(true, u8);
    
            println!("Option<bool>:");
            dbg_bits!(None::<bool>, u8);
            dbg_bits!(Some(false), u8);
            dbg_bits!(Some(true), u8);
    
            println!("Option<Option<bool>>:");
            dbg_bits!(Some(Some(false)), u8);
            dbg_bits!(Some(Some(true)), u8);
            dbg_bits!(Some(None::<bool>), u8);
            dbg_bits!(None::<Option<bool>>, u8);
    
            println!("Option<&i32>:");
            dbg_bits!(None::<&i32>, usize);
            dbg_bits!(Some(&0i32), usize);
        }
    }