💎

【Crystalと○○】Crystalとクラス変数

2021/04/19に公開

📖【Crystalと○○】コンテンツ一覧

Crytalでは値を保持する枠組みとして、ローカル変数、インスタンス変数、クラス変数と定数を利用できます、今回はこの中からクラス変数の使い方や特徴を説明することにしましょう。

クラス変数は、特定のインスタンスに固有の情報を保持するインスタンス変数に対して、クラス変数はクラスなどの型に対して固有の情報を保持する目的で定義される変数です。クラス 変数という名前ですが、実はクラスだけでなく構造体やモジュール、列挙型などにも定義可能です。構造体変数やモジュール変数などとそれぞれ呼び変えると面倒なので、全てまとめて クラス 変数と呼バレます。

クラス変数の命名規則

クラス変数の変数名は @@ に続く小文字のスネークケースで記載します。

class SomeClass
  @@class_var : String
end

クラス変数の型と初期化

インスタンス変数と同様、クラス変数もコンパイル時点で型の特定が必要な変数です。明示的な型の指定方法や、明示的に指定されなかった初期値の代入時に型の推論が行われる点、型だけが指定されて初期化されなかったクラス変数が暗黙的に nil 扱いになり、型が nilable でなければ型チェックでエラーになる点もインスタンス変数と同様です。

ただし、クラス変数にはコンストラクタに相当する仕組みがありませんので、初期化はすべて型定義の直下で行うことになります。

class SomeClass
  @@class_var = 1
end

クラス変数のスコープ

クラス変数は、定義された型のクラスメソッドやインスタンスメソッド内で利用でき、そのスコープは基本的に クラス変数が定義された型 の内部です。

クラス変数がインスタンスメソッド内で使用された場合は、複数のインスタンスから同じ値にアクセス可能です。

class SomeClass
  @@class_var = 1

  def up
    @@class_var += 1
  end
end

a = SomeClass.new
b = SomeClass.new

a.up #=> 2
b.up #=> 3

ただし、クラス変数を定義した型を継承もしくはmix-inした型では同じクラス変数を使用することが可能です。

class OtherClass < SomeClass
end

c = OtherClass.new
c.up #=> 4

ジェネリック型のクラス変数

ジェネリック型におけるクラス変数の扱いにには少々注意が必要です。

たとえ実際に使用する際の型引数が違ったとしても、あるジェネリック型で定義されたクラス変数は1つです。

例えば、GenericClass(T) 型が実際にコード内では、GenericClass(String) 型、GenericClass(Int32) 型として使用されていたとしても、GenericClass(T) 型で定義されているクラス変数 @@class_var は両方の型で共有することになります。

class GenericClass(T)
  @@class_var = 1

  def up
    @@class_var += 1
  end
end

GenericClass(String).new.up #=> 2
GenericClass(Int32).new.up  #=> 3

今回のまとめ

  • クラス変数は @@ で始まる、型が持つ変数
  • 継承先やmix-in先からも参照可能
  • ジェネリック型の場合、型引数が違っても同じ値を共有

Discussion