💎

【Crystalと○○】Crystalとグローバル変数?

2021/04/19に公開

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

Crytalにはグローバル変数は存在しません。

以前は、CrystalにもRubyと同じ $var という構文でグローバル変数が利用可能だった時代もありましたが、2016年9月2日に公開されたv0.19.0で、ユーザが定義可能なグローバルスコープを持った変数は言語仕様から取り除かれました。

v0.35.1時点で $ から始まる変数として利用可能なのは、以下の3種類のみです。

  1. 直前に行われた正規表現(Regex 型)のマッチが成功した場合にその結果(Regex::MatchData 型)を返す $~
  2. 直前に行われた正規表現のマッチが成功した場合に、N番目のキャプチャ結果を返す $N(N=1〜)
  3. 直前に実行された子プロセスの終了ステータス(Process::Status 型)を返す $?

これらは特殊変数(special variables)と呼ばれ、表記こそ $ で始まりますが、実際にはメソッドローカルのスコープしか持ちませんので、あらゆる意味で グローバル変数 ではありません。

グローバルスコープを持った状態の管理

Crystalでグローバルスコープを持った状態の管理方法はいくつか考えられますが、代表的なものは以下の2種類です。

定数を利用する(個人的に非推奨)

型定義の外側で定義された定数はグローバルなスコープを持ちますし、型の内部で定義された定数も型の名前空間を通じてその外側から直接アクセスが可能です。

定数自体は一度初期化してしまうと別のオブジェクトを再代入することができませんが、初期化した値の状態が変化することは許容します。

例えば、配列(Array(T) 型)やハッシュ(Hash(K,V) 型)などのコレクションであれば、定数が保持するオブジェクトを変更せず、内部に保持するオブジェクトの追加や削除が可能です。

module SomeModule
  CONST = ["a"]
end

SomeModule::CONST[0]
#=> "a"

SomeModule::CONST[0] << "b"
#=> ["a", "b"]

とはいえ、オブジェクトの直接変更が可能な定数は、設計者の意図しない変更が行われる可能性がるため注意が必要です。

クラス変数を使用する

クラス変数はクラスや構造体といった型自身が持つ変数です。型はプログラム全体を通して一意に特定できますので、クラス変数の値はグローバルな環境でも一意に定まります。クラス変数のスコープはあくまでその型の内部なので、本来は型の外側から直接操作できませんが、クラスメソッドとしてアクセサを定義することで型の外側へのインタフェースを提供することができます。

module SomeModule
  @@global_use = 1

  def self.global_use
    @@global_use
  end

  def self.global_use=(value)
    @@global_use = value
  end
end

SomeModule.global_use #=> 1

SomeModule.global_use = 2

SomeModule.global_use #=> 2

クラス変数は再代入が可能なため、定数よりも柔軟な利用が可能です。

また、アクセサによってカプセル化することで更新時に値の正規化やフォーマットチェックも可能となります。

こういった特徴から、個人的には定数であることに特別な意味がある場合や、余程頻繁に利用されてパフォーマンスに影響するような場合を除けば、定数を使うよりもクラス変数を使用するほうがお勧めです。

今回のまとめ

  • Crystalには所謂グローバル変数は存在しない。
  • クラス変数へのアクセサメソッドを用意すると、グローバルな状態の管理も可能。

Discussion