のらブログ

sadanoraが思ったことを書きます

【Ruby】uninitialized constant (NameError)にハマった

uninitialized constant (NameError)とは

"uninitialized constant"はそのまま日本語にすると「定義されていない定数」という意味になる。

呼び出されたクラスやメソッドが定義されていないことでエラーが発生しているということなので、何かしらの理由でクラスやメソッドの呼び出しが出来ていない。

エラーの出力例としてはこんな感じ

/Users/user/project/sample.rb:7:in `<module:LS>': uninitialized constant LS::Command (NameError)

  class ShortFormat < Command
                      ^^^^^^^
#--以下バックトレースが続くが省略--

この場合は「LS::Commandクラスが定義されてないので呼び出せない」ということになる。

解決方法

解決方法はエラーが発生しているクラスやメソッドが「なぜ呼び出せないのか」を特定して、呼び出せるようにする必要がある。

この「なぜ呼び出せないのか」が特定できず、ハマった。

「requireの相互参照」が原因だったのだけれど、"uninitialized constant (NameError)"とかでググってもエラーの原因としてrequireを挙げている情報を見つけられず*1、「requireの相互参照」という言葉も知らなかった(又は聞いたことはあったのかもしれないが思いつかなかった)ので途方にくれた。

requireの相互参照とは

2つのファイル間でお互いのファイル同士をrequireすること

たとえば以下のようにお互いがお互いをrequireしていること。*2

エラーの発生しない相互参照

foo.rb

require_relative 'bar'

class Foo
end

bar.rb

require_relative 'foo'

class Bar
end

irbでrequireしてみる。

$ irb
>> require_relative 'bar'
=> true
>> require_relative 'foo'
=> false

後に実行したrequire_relative 'foo'はfalseになっている。

るりまによると、

ライブラリのロードに成功した時には true を返し、ロードした feature の名前を(拡張子も含めて) 変数 $" に追加します。ただし、feature の名前が既に $" に含まれていた場合はロードせずに false を返します。

ということなので、2回目の場合にはすでにロードされているのでfalseが返っている。

Kernel.#require (Ruby 3.1 リファレンスマニュアル)

エラーの発生する相互参照の例

たとえば以下2つのファイルを作り、irbでrequireするとuninitialized constant (NameError)が発生する。

coke.rb

require_relative 'fanta'

module Soda
  class Coke
  end
end

fanta.rb

require_relative 'coke'

module Soda
  class Fanta < Coke
  end
end
$ irb
>> require_relative 'coke'
/Users/user/project/fanta.rb:4:in `<module:Soda>': uninitialized constant Soda::Coke (NameError)
                                                           
  class Fanta < Coke                                       
                ^^^^            
#--以下略--

エラーを回避するには

サンプルコードのような継承が必要な設計はたぶんおかしい。

設計を見直す。

「継承より委譲」

まとめ

このエラーに当たったのは、フィヨルドブートキャンプの課題のひとつ「lsコマンドを作る(オブジェクト指向版)」に取り組んでいるときでした。 モジュールや継承などについて理解が浅いことが重なり「どうにもならんーー」という感じになったのですが、メンターさんに質問したところ回答を得ることができました。

参考

TIPS/requireの相互参照 - やってよ!るby!! - atwiki(アットウィキ)

*1:なのでこのブログを書いている

*2:この記事のコードはすべてRuby3.1.1で検証しています