コンテンツにスキップ

列挙型 (Enum)

列挙型 (Enum) は整数の集合で、そのそれぞれの値が名前を持っています。例をあげます。

enum Color
  Red
  Green
  Blue
end

enum キーワードに続けてその名前を書くことで、列挙型を定義できます。列挙型はその本体に値を持ちます。値は最初に 0 から始まって、1ずつ増えていきます。それらのデフォルト値を上書きすることも可能です。

enum Color
  Red        # 0
  Green      # 1
  Blue   = 5 # 5に上書きする
  Yellow     # 6 (5 + 1)
end

列挙型のそれぞれの定数はその列挙型を型として持ちます。

Color::Red # :: Color

紐付いている値を参照するには value を呼び出してください。

Color::Green.value # => 1

この値の型は Int32 ですが、変更することができます。

enum Color : UInt8
  Red
  Green
  Blue
end

Color::Red.value # :: UInt8

ただし、整数型しか指定することはできません。

すべての列挙型は Enum を継承しています。

フラグ列挙型

列挙型に @[Flags] を指定できます。これを指定するとデフォルトの値が変わります。

@[Flags]
enum IOMode
  Read  # 1
  Write # 2
  Async # 4
end

@[Flags] アノテーションを指定することで、最初の定数の値は 1 になり、以降は前の値を 2 倍したものになります。

また、暗黙の定数として NoneAll が自動的に列挙型に追加されます。ここで None の値は 0 で、All はすべての定数のビット ORを取ったものになります。

IOMode::None.value # => 0
IOMode::All.value  # => 7

さらに、Enum のメソッドには @[Flags] アノテーションを指定することで振る舞いを変えるものがいくつかあります。例をあげます。

puts(Color::Red)                    # prints "Red"
puts(IOMode::Write | IOMode::Async) # prints "Write, Async"

整数から列挙型を作る

整数から列挙型を作ることができます。

puts Color.new(1) # => "Green" と出力

列挙型の定数にない値を指定することも可能です。その場合は、あくまでも型は Color となりますが、出力してときにはその値がそのまま出力されます。

puts Color.new(10) # => "10" と出力

これは主に C の整数を Crystal の列挙型に変換する用途で利用します。

メソッド

クラスや構造体と同様に、列挙型にもメソッドを定義することが可能です。

enum Color
  Red
  Green
  Blue

  def red?
    self == Color::Red
  end
end

Color::Red.red?# => true
Color::Blue.red?# => false

クラス変数を使うこともできますが、インスタンス変数を使うことはできません。

使い方

列挙型は Symbol の型安全の代替物として利用できます。例えば、列挙型を API のメソッドの型制約に指定することができます。

def paint(color : Color)
  case color
  when Color::Red
    # ...
  else
    # あまり考えられないが、絶対にないとは言えない
    raise "unknown color: #{color}"
  end
end

paint Color::Red

上記と同等のコードを、シンボルを使って実装することもできます。

def paint(color : Symbol)
  case color
  when :red
    # ...
  else
    raise "unknown color: #{color}"
  end
end

paint :red

ただ、この場合はもしプログラマが :reed とタイポしてしまったとすると、実行時にエラーが発生することになります。一方、Color::Reed であればコンパイルエラーとなります。

したがって、列挙型を利用できるときには常に列挙型を利用することを推奨します。シンボルを使うのは API の内部的な実装に留めて、公開する API ではシンボルを使わないようにしましょう。ただし、最終的には各自が自由に判断できることなので、必ずそうしなければならないというわけではありません。