目次

モジュール

このチュートリアルでは数行程度の僅かなコードのプログラムしか作ってきませんでした。現実的なプログラムではもっと多くのコードが必要になります。多くの場合は一つのファイルに全てのコードを詰め込むよりも、そのコードの特性によって複数のファイルに分割したほうが管理しやすくなります。それぞれのファイルは独立したコンパイル単位、つまり、別々にコンパイルされます。そして、通常は一つのファイルが一つのモジュールになります。

インポート

モジュールはインポートして使用します。

例えば、myAppというプログラムがあったとして、次のような構成になっていたとします。

myApp/
├── main.nim
├── moduleA.nim
├── moduleB.nim
└── other/
    ├── moduleA.nim
    └── moduleB.nim

そして、main.nimはmoduleAとmoduleBを使用し、moduleAはotherのmoduleAを、moduleBはotherのmoduleBを使用していたとします。このような依存関係があるとき、各ファイルでimport文を使用して、それぞれ必要とするモジュールをインポートします。

# main.nim
import moduleA
import moduleB
...

# moduleA.nim
import other/moduleA
...

# moduleB.nim
import other/moduleA

# other/moduleA.nim
...

# other/moduleB.nim
...

このプログラムをビルドするには、トップレベルのソースファイルであるmain.nimだけをコンパイラに渡します。依存するモジュールの解決とソースファイルのコンパイルは自動的に行われます。

$ cd /path/to/myApp
$ nim compile --out:myApp main.nim 
Hint: used config file '/home/freemikan/.choosenim/toolchains/nim-2.0.2/config/nim.cfg' [Conf]
Hint: used config file '/home/freemikan/.choosenim/toolchains/nim-2.0.2/config/config.nims' [Conf]
........................................................................
Hint:  [Link]
Hint: mm: orc; threads: on; opt: none (DEBUG BUILD, `-d:release` generates faster code)
27620 lines; 0.120s; 30.332MiB peakmem; proj: /home/freemikan/code/NimTutorial/myApp/main.nim; out: /home/freemikan/code/NimTutorial/myApp/myApp [SuccessX]

メッセージに含まれる“out: /home/freemikan/code/NimTutorial/myApp/myApp [SuccessX]”の部分から、正常にmain.rsがコンパイルされて、myAppが生成されたことが確認できます。1)

エクスポート

モジュール内で定義されたシンボル、つまり、変数やプロシージャはデフォルトではモジュール外からアクセスできません。アクセスできるようにするには、エクスポートすることを明示しなければなりません。エクスポートするには、名前に*をつけます。

# moduleA.nim
let foo* = 100
let bar = 200

# main.nim
import moduleA
echo foo    # OK => 100
echo bar    # コンパイルエラー!

上の例では、foo**が付与さているのでエクスポートされます。barはそうではないのでエクスポートされません。moduleAをインポートするmain.nimでは、fooにのみアクセス可能で、barはアクセス不可です。main.nimをコンパイルするとエラーになります。

Error: undeclared identifier: 'bar'

main.nimではbarが全く見えていないことが分かります。

インポートされるシンボルは直接インポートするモジュール内でエクスポートされているものだけです。

# other/moduleA.nim
let bar* = 200

# moduleA.nim
import other/moduleA
let foo* = 100

# main.nim
import moduleA
echo foo    # OK => 100
echo bar    # コンパイルエラー!

main.nimからはother/moduleAでエクスポートされているbarを認識することが出来ません。もし、moudleAがインポートしたother/moduleAもmain.nimで使えるようにしたいなら、その判断はmoduleAに委ねられます。moduleAがother/moduleAを再エクスポートすれば可能となります。

# other/moduleA.nim
let bar* = 200

# moduleA.nim
import other/moduleA
export moduleA    # other/moduleAを再エクスポート
let foo* = 100

# main.nim
import moduleA
echo foo    # OK => 100
echo bar    # OK => 200

定数やプロシージャについても同じ規則に従います。

# other/moduleA.nim
const BAZ* = 300
proc add3*(a, b, c: int): int = a + b + c

# moduleA.nim
import other/moduleA
const FOO* = 100
const BAR = 200
proc add2*(a, b: int): int = a + b
proc sub2(a, b: int): int = a - b

# main.nim
import moduleA
echo FOO             # OK => 100
# echo BAR           # コンパイルエラー!
# echo BAZ           # コンパイルエラー!
echo add2(1, 2)      # OK => 3
# echo sub2(1, 2)    # コンパイルエラー!
# echo add3(1, 2, 3) # コンパイルエラー!

まとめると次のとおりです。

このようにかなりシンプルな規則となっています。

標準ライブラリ

標準ライブラリもモジュールの仕組みを使って提供されていますので、使用したいものがあればまずインポートする必要があります。ただし、systemモジュールは例外で、自動的にインポートされます。このモジュールにはintboolなどの基本的な型、arrayseqとそのプロシージャなど、必要不可欠なものが含まれています。

他にどのようなモジュールが提供されているかは、標準ライブラリのドキュメントがよく整理されているので、そちらを参照すると良いでしょう。

1)
コンパイラは中身が空のモジュールに対して警告が出力しますが、今は重要ではありません。