【Crystal】releaseフラグの警告の謎、crystalコマンドの罠

10197 ワード

最初に

久しぶりにCrystalを触って詰まって、自分のブログ記事に助けられた。
その2021年9月のブログ記事を改めて編集し、ここにも投稿する。

内容は、下記の内容です。

  • benchmarkライブラリを使ったときの--releaseフラグの警告の謎
  • 上記の原因、crystalコマンドの説明、runサブコマンドの有無の挙動の違い等

動作環境

なお、本記事は、Macで最新のCrystal 1.4.1(2022-04-22)で検証。

表記揺れの注意

本記事は、フラグとオプションという言葉の表記が揺れている。
Crystalでオプション(フラグ)の有無はflag?マクロで確かめたりエラー文で「flag」と書かれたので、フラグという表現も用いているが、一般にはオプションだと思われる。

benchmarkを使うと、謎の警告がでる。

require "benchmark"
Benchmark.bm{}

上記のように必要最小限にbenchmarkライブラリを使ったコードを実行してみる。

$ crystal benchmark.cr
Warning: benchmarking without the `--release` flag won't yield useful results

なんか警告がでる。読んで見ると、
--releaseフラグをつけないベンチマーク計測は、意味のある結果をださない。
という警告がでている。

--releaseをつけると、リリースモードを有効にして、生成されるバイナリに最適化をしてコンパイルしてくれるらしい。
最適化してない状態で計測しても意味がないでしょ、リリースモードにして、ということだった。

言われたとおり、コマンドの実行の際に--releaseフラグをつけて実行してみる。

$ crystal benchmark.cr --release
Warning: benchmarking without the `--release` flag won't yield useful results

--releaseフラグをつけて実行すれば消えるかと思ったが、この警告は消えなかった。
常に出続けるタイプの警告なのだろうか?(結論そういうわけではなかった)

他に、--releaseフラグの位置をかえてファイル名より先にすると、
存在しないコマンド扱いされてエラーとなった。

$ crystal --release benchmark.cr
Error: unknown command: --release

なお、Rubyだと、rubyコマンドとファイル名の間にオプションを書いて動く。
rubyコマンドについては、最後の方で参考に記述する。

解決方法: runサブコマンドをつける

解決方法を先にいうと、下記のようにrunサブコマンドをつけて実行すると、警告は消え正しく実行される。

$ crystal run benchmark.cr --release
$ crystal run --release benchmark.cr

--releaseオプションの位置は、どちらでもいいみたい。

このあとの記事では、crystalコマンドの挙動等について調べたことを書く。

最初にcrystalコマンドの説明を見てみる

多くのコマンドがそうであるようにcrysltal --helpとすれば、
crystalコマンドの簡単な説明を得ることができる。
crystal -hcrystal helpでもよい。

$ crystal --help
Usage: crystal [command] [switches] [program file] [--] [arguments]

Command:
    init                     generate a new project
    build                    build an executable
    docs                     generate documentation
    env                      print Crystal environment information
    eval                     eval code from args or standard input
    i/interactive            starts interactive Crystal
    play                     starts Crystal playground server
    run (default)            build and run program
    spec                     build and run specs (in spec directory)
    tool                     run a tool
    help, --help, -h         show this help
    version, --version, -v   show version

Run a command followed by --help to see command specific information, ex:
    crystal <command> --help

これを見ると、それぞれのコマンドの説明が見れる。
「run (default)」と書いてあり、サブコマンドを省略するとrunが適用されるようにみえる。
しかし、実際には省略するとオプションの扱いが変わり、全く同じではない。罠だと思う。

なお、このhelpの説明を見ると、「crystal <command> --help」とあり、
crystal run --helpの形でサブコマンドのもう少し詳しい説明を読むことができる。

コンパイラの使い方 - Crystal
なお、日本で詳しい説明を見たければ、上記のページを見ればよさそう。

runサブコマンドの有無の違い

$ crystal run foo.cr --release
$ crystal foo.cr --release

自分は、上と下が同じに違いないと思った。しかし、実際には違った。
上のrunコマンドで呼び出した場合は、--releaseオプションが機能する。
しかし、下の場合は、--releaseオプションが機能しなかった。

これについて、以前CrystalコミュニティのDiscordで雑に尋ねてみたところ、
z64さんという方に「昔はrunコマンドなしで、--releaseオプションが機能した」と教えてもらった。
なぜ削除したのか質問してみたが、正確に覚えてないとのことだった。
ただcrystal foo.cr --releaseの場合は、crystalコマンドのオプションではなく、ファイルのオプションとして認識されるということだった。

{% if flag?(:release) %}
  puts ["release flag", ARGV]
{% else %}
  puts ["no release flag", ARGV]
{% end %}

上記の内容のファイルを作り、比べてみると分かりやすい。
ここではflag_test.crというファイル名にした。

$ crystal run flag_test.cr --release
["release flag", []]

$ crystal flag_test.cr --release 
["no release flag", ["--release"]]

runコマンドの方は、crystalコマンドのオプションとして--releaseが認識されていてflag?マクロで認識できるがARGVには残っていない。
対して、runを省略した場合は、crystalコマンドのオプションとして認識されずARGVに残る。
(※ なお、ARGVというのは、コマンドの引数が入るところ。)

というわけで、上記のような違いがあった。

ところで、rubyコマンドの挙動

ところで、rubyコマンドの場合はどうなのだろう。

a = 1
p ARGV

例えば、上記のv.rbで確かめてみる。

$ ruby v.rb
[]

$ ruby v.rb -w
["-w"]

$ ruby -w v.rb   
v.rb:1: warning: assigned but unused variable - a
[]

オプションが機能するのは、rubyコマンドとファイル名の間。
-wオプション付きでRubyで実行すると、警告が表示されてるのがわかる。

ファイル名の後ろに-wがあると、警告はでず、ARGVに格納されている。
このとき、オプションではなく、コマンド引数として認識されている、のがわかる。

改めて確かめると、rubyコマンドも位置でオプションかどうか区別されて機能するか決まるので、注意が必要だと感じた。

まとめ

Rubyと違って、Crystalはrunサブコマンドを書いた方がよさそう。

最初はrun省略時に何も認識されてないように見え、修正されるべきバグか何かだと思った。
しかし、Crystalコミュニティで質問してみるとファイルのオプション(コマンド引数)として認識されているとのことだった。
初見殺しな感じでわかりにくい気もするが、もしかしたら何か使い分けできて便利かもしれない。

今のところrun省略時のオプションが機能しないのは非直感的で、何の役の立つのかわかってないので、特に嬉しくはない……。