vim の :rubyfile は rb_load を使うが ruby の load は rb_f_load を使う
事の発端
if_ruby を使って何かしたいなぁということで :rubyfile test.rb
を実行したが、LoadError: cannot load such file -- test.rb
となって実行できなかった。ただ、:ruby load "test.rb"
は実行できるので、何が違うのか?という疑問が事の発端
vim-jp の slack で質問
slack で質問してみると :rubyfile
は rubyの公開APIの rb_load()
を呼んでいるだけとのこと。また、ruby load
は Kernel.#load‘ を使用しているが、なぜ
:rubyfile` のほうが動かないのかはわからないとのことで原因を追究してみたくなった。
Kernel.#load は rb_load
を実行しているのか?
2.6のブランチの load.c
を見ると、 load
は rb_f_load()
をコールしているようです。
https://github.com/ruby/ruby/blob/ruby_2_6/load.c#L1233
rb_load
も load.c
に実装があり、単に rb_load_internal()
をコールしているのみとなっているようです。
https://github.com/ruby/ruby/blob/ruby_2_6/load.c#L659
rb_f_load()
の中でも rb_load_internal()
をコールしていますが、前処理がいくつか入っているため、その部分に今回の原因がありそう?
rb_f_load の前処理を追う
追うといっても前処理で生成されている文字列を一個一個printfで出力してみるというだけのことをしました。
static VALUE rb_f_load(int argc, VALUE *argv) { printf("rb_f_load start\n"); // 追加 VALUE fname, wrap, path, orig_fname; rb_scan_args(argc, argv, "11", &fname, &wrap); printf("fname(1): %s\n", StringValueCStr(fname)); // 追加 file_to_load(fname); orig_fname = rb_get_path_check_to_string(fname, rb_safe_level()); printf("orig_fname:%s\n", StringValueCStr(orig_fname)); // 追加 fname = rb_str_encode_ospath(orig_fname); printf("fname(2): %s\n", StringValueCStr(fname)); // 追加 RUBY_DTRACE_HOOK(LOAD_ENTRY, RSTRING_PTR(orig_fname)); path = rb_find_file(fname); if (!path) { printf("path is null?? \n"); // 追加 if (!rb_file_load_ok(RSTRING_PTR(fname))) load_failed(orig_fname); path = fname; } printf("path: %s\n", StringValueCStr(path)); // 追加 printf("wrap: %d\n", RTEST(wrap)); // 追加 rb_load_internal(path, RTEST(wrap)); RUBY_DTRACE_HOOK(LOAD_RETURN, RSTRING_PTR(orig_fname)); printf("rb_f_load start\n"); // 追加 return Qtrue; }
結果的には、load "foo.rb"
のようなスクリプトで文字列が変化することはなく。rb_load_internal()
には foo.rb
という文字列がそのまま渡されているようでした。wrap
の値が怪しいのかな?とも思って見てみましたが、それも if_ruby の値と同じでした。
rb_load の実装を見てみる
rb_load()
はrb_load_internal()
をコールしているだけといいましたが、実際には引数で渡ってきたパスを一旦file_to_load()
関数に渡していました。
void rb_load(VALUE fname, int wrap) { rb_load_internal(file_to_load(fname), wrap); }
file_to_load()
の実装は以下の通りです。FilePathValue()
ではエラーになってなかったっぽいので、rb_find_file()
でエラーになって返り値が0になるため、load_failed()
が呼ばれてしまうようです。
static VALUE file_to_load(VALUE fname) { VALUE tmp = rb_find_file(FilePathValue(fname)); if (!tmp) load_failed(fname); return tmp; }
rb_find_file
は rb_f_load()
の中でも使われており、返り値が0の場合の処理が rb_load()
とは異なっているようです。
path = rb_find_file(fname); if (!path) { if (!rb_file_load_ok(RSTRING_PTR(fname))) load_failed(orig_fname); path = fname; }
rb_file_load_ok()
という関数がキモな気がしてきました。
rb_file_load_ok()
は file.c
内に定義されています。ただ、内容を見た感じ、fdを取得して内部でロード可能かを判断しているだけのようなので、特別何があるというわけでもなさそう。カレントディレクトリにロード対象のファイルがあれば、それをちゃんとロード可能だと判断してくれそうな気がします。というかしてくれます。
結論
Kernel.#load
は rb_f_load()
を呼んでいて、vimの:rubyfile
はrb_load()
を呼んでいる。rb_load()
の中では file_to_load()
が使われていて、この関数の中のrb_find_file()
が返り値0を返すためロードエラーとなっている。rb_f_load()
でも同関数が使われている。ただ、一度ロードには失敗するが、そのあとの処理でrb_file_load_ok()
でロード可能と判断されるため正常にロードされる。
なぜ、このような違いがあるのかまでは今回は追えなかったが、一応 :rubyfile
が ruby load
と異なる動作をする理由がわかったので今回は終了とする。