第2章
Mojo のプログラムはどう動くか
ソースコードが機械語になって動くまでの流れをつかみ、run と build の時間を測ります。プログラムファイルの組み立て方、ファイルの分割、標準ライブラリの読み込み、mojo format、エラーの種類、mojo のサブコマンドを学びます。
この章の内容
第1章では、Mojo を入れて最初のプログラムを動かしました。この章では、mojo run を押した裏側で何が起きているのかを知り、プログラムをファイルとして組み立てるときの基本を身につけます。
この章を終えると、次のことができるようになります。
- ソースコードが実行されるまでの流れ(コンパイルの段階)を説明できる
mojo runとmojo buildの違いを、実際に時間を測って説明できる- 関数・コメント・ドキュメント文字列を使って、プログラムファイルを組み立てられる
- コードを2つのファイルに分け、自分のモジュールと標準ライブラリを
importできる mojo formatでコードの書式をそろえられる- コンパイル時のエラーと実行時のエラーを見分け、終了コードを確かめられる
この章から、コマンドは pixi shell の中で実行する前提で、pixi run を付けずに mojo ... とだけ書きます。
2.1ソースコードが機械語になるまで
Python では、python hello.py と打つと、インタープリターがファイルを読み、1行ずつ解釈しながら実行しました。Mojo はまったく違います。実行の前に、ファイル全体を コンパイル して、CPU がそのまま実行できる 機械語 に翻訳します。
コンパイラの中では、おおまかに次の段階を順にたどります。
- 構文解析と型チェック:ソースコードを読んで、文法の誤りや型の食い違いがないかを調べます。第1章で見たエラーは、すべてこの段階で見つかったものです。
- MLIR:問題がなければ、Mojo 向けの中間表現(MLIR)に変換します。ここで Mojo 独自の最適化が行われます。
- LLVM IR:さらに、LLVM という共通の基盤が使う中間表現に変換します。C++ や Rust のコンパイラも同じ LLVM を使っています。ここで、LLVM が長年育ててきた最適化が適用されます。
- 機械語:最後に、その CPU 向けの命令に変換します。
この4段階の中身を理解する必要はありません。大切なのは「実行の前に、プログラム全体が機械語まで翻訳される」という点です。Python のように、実行しながら解釈することはありません。
中間の結果をのぞいてみる
mojo build には、途中の段階の結果を取り出すオプション --emit があります。短い関数のあるプログラムで試してみましょう。
def add(a: Int, b: Int) -> Int:
return a + b
def main():
print(add(2, 3))~$ mojo run add.mojo 5 ~$ mojo build --emit llvm add.mojo -o add.ll ~$ head -n 2 add.ll ; ModuleID = 'add.mojo' source_filename = "add.mojo" ~$ mojo build --emit asm add.mojo -o add.s ~$ wc -l add.ll add.s 670 add.ll 482 add.s 1152 total
最初の mojo run は、いつもどおり 5 を表示しました。次の2つのコマンドは、実行ファイルではなく、LLVM IR(add.ll)とアセンブリ言語(add.s、機械語を人間が読める形にしたもの)を書き出しています。head で見えているとおり、LLVM IR はただのテキストファイルです。
書き出した行数も見てください。たった5行の Mojo プログラムが、数百行の LLVM IR とアセンブリになっています。print や文字列の処理など、標準ライブラリの部品も一緒に翻訳されるためです。本書ではこれ以上は読みませんが、「コンパイルとは、こうした中間の形を何段階か経て機械語を作ること」というイメージを持っておいてください。
2.2mojo run と mojo build の時間を測る
第1章では、mojo run はコンパイルしてすぐ実行し、mojo build は実行ファイルを残すと学びました。では、時間はどれだけ違うのでしょうか。実際に測ってみます。
まず、計算に少し時間のかかるプログラムを用意します。0 から 10 億未満までの整数を 7 で割った余りを、すべて足し合わせるプログラムです。
def main():
var total: UInt64 = 0
for i in range(1_000_000_000):
total += UInt64(i) % 7
print(total)UInt64 は 0 以上の 64 ビット整数です(型は第3章で学びます)。1_000_000_000 のように、数字の間に _ を入れて読みやすくできるのは Python と同じです。
bash の time コマンドを前に付けると、実行にかかった時間が表示されます。
~$ time mojo run sumloop.mojo 2999999997 real 0m1.579s user 0m1.507s sys 0m0.091s
real が、実際に経過した時間(コマンドを打ってから終わるまでの壁時計の時間)です。user は CPU がプログラムの計算に使った時間、sys は OS の仕事に使った時間で、複数のスレッドが同時に動くと user が real より大きくなることもあります。ここでは real の値だけを見ます。この実行では、10 億回のくり返しを含めて、全体で約 1.6 秒かかりました。
次に、ビルドして、できた実行ファイルを測ります。
~$ time mojo build sumloop.mojo real 0m0.639s user 0m0.585s sys 0m0.068s ~$ time ./sumloop 2999999997 real 0m0.719s user 0m0.716s sys 0m0.004s ~$ time ./sumloop 2999999997 real 0m0.687s user 0m0.684s sys 0m0.004s
mojo build はコンパイルだけの時間(約 0.6 秒)、./sumloop はプログラムの実行だけの時間(約 0.7 秒)です。足すと約 1.3 秒で、mojo run の約 1.6 秒とおおよそ合います。mojo run の時間は、おおよそ「コンパイルの時間 + 実行の時間」と考えられます(時間は測るたびに少しずれます)。実行ファイルは2回目以降もコンパイルが要らないので、2回とも同じくらいの時間で終わっています。
実行にほとんど時間がかからない hello プログラムでも比べてみましょう。
def main():
print("Hello")~$ time mojo run hello.mojo Hello real 0m0.564s user 0m0.537s sys 0m0.041s ~$ mojo build hello.mojo ~$ time ./hello Hello real 0m0.009s user 0m0.000s sys 0m0.010s
print を1回するだけのプログラムなのに、mojo run には約 0.56 秒かかりました。そのほとんどはコンパイルの時間です。一方、ビルドした ./hello は約 0.009 秒、つまり 10 ミリ秒以下で終わります。
ここから、使い分けの目安が見えてきます。
| 場面 | 向いているもの | 理由 |
|---|---|---|
| コードを書き換えながら試す | mojo run | 毎回コンパイルが必要なので、手順が少ないほうが楽 |
| 同じプログラムを何度も動かす | mojo build | コンパイルは1回で済み、あとは実行時間だけ |
| 速さを測る | mojo build | コンパイルの時間が混ざらない |
2.3プログラムファイルの組み立て
ここまでは数行のプログラムでしたが、少し大きくなると、ファイルの中身を整理する必要が出てきます。Mojo のプログラムファイルは、ふつう次の部品でできています。
- ファイルの説明(ドキュメント文字列)
- 補助的な関数
- 最後に、実行の入口の
main関数
"""Compute the area of a circle."""
def square(x: Float64) -> Float64:
"""Return x multiplied by itself."""
return x * x
def circle_area(radius: Float64) -> Float64:
"""Return the area of a circle with the given radius."""
# 円周率は 3.14159 で近似する
return 3.14159 * square(radius)
def main():
var r = 2.0 # 半径
print("radius:", r)
print("area:", circle_area(r))~$ mojo run circle.mojo radius: 2.0 area: 12.56636
上から順に見ていきましょう。
- 先頭の
"""..."""は、ファイル全体の説明です。このような、説明のための文字列を ドキュメント文字列(docstring)と呼びます。Python と同じく、ファイル・関数・型の先頭に置きます。 def squareとdef circle_areaは補助的な関数です。引数と戻り値にはx: Float64、-> Float64のように型を書きます。関数については第10章で詳しく学ぶので、ここでは「def 名前(引数: 型) -> 戻り値の型:と書く」とだけ覚えてください。#から行末まではコメントです。コメントは実行結果に何も影響しません。var r = 2.0 # 半径のように、コードのあとに書くこともできます。mainは、補助的な関数より前に書いても後に書いても動きます。ただ、mainを最後に置くと「上から読めば全体が分かる」ので、本書ではこの順に書きます。
出力を読むと、radius: 2.0、area: 12.56636 と表示されています。2.0 * 2.0 * 3.14159 が 12.56636 です。
ドキュメント文字列の書き方に決まりがある
Mojo のコンパイラは、ドキュメント文字列の書き方もチェックします。日本語で説明を書いてみましょう。
"""テスト用のモジュール。"""
def double(x: Int) -> Int:
"""x を 2 倍にして返す。"""
return x * 2
def main():
print(double(21))~$ mojo run jpdoc.mojo /home/user/jpdoc.mojo:1:4: warning: doc string summary should end with a period '.', exclamation mark '!', question mark '?', or backtick '`', but this ends with '�' """テスト用のモジュール。""" /home/user/jpdoc.mojo:5:8: warning: doc string summary should begin with a capital letter or non-alpha character, but this begins with 'x' """x を 2 倍にして返す。""" /home/user/jpdoc.mojo:5:8: warning: doc string summary should end with a period '.', exclamation mark '!', question mark '?', or backtick '`', but this ends with '�' """x を 2 倍にして返す。""" 42
プログラムは動いて 42 と表示されましたが、その前に3つの 警告(warning)が出ています。エラーとは違い、警告が出ても実行は止まりません。
警告は「ドキュメント文字列の1行目(要約)は、大文字で始めて、ピリオド . か ! ? で終えなさい」と言っています。日本語の句点「。」はこの決まりに合わないので、文字化けした記号とともに警告されてしまいます。コード中のドキュメント文字列は、本書のように 英語の要約を1文で 書き、補足の説明は # コメントに日本語で書くのが、いちばん素直な書き方です。
2.4ファイルを分けて import する
プログラムが大きくなったら、関連する関数を別のファイルに移します。ファイルが1つの モジュール になり、import で使えます。さきほどの square と circle_area を、geometry.mojo に移してみましょう。
"""Geometry helpers."""
def square(x: Float64) -> Float64:
"""Return x multiplied by itself."""
return x * x
def circle_area(radius: Float64) -> Float64:
"""Return the area of a circle with the given radius."""
return 3.14159 * square(radius)同じ shapes フォルダーに、使う側のファイル main.mojo を置きます。
from geometry import square, circle_area
def main():
print(square(3.0))
print(circle_area(2.0))~$ cd shapes ~/shapes$ ls geometry.mojo main.mojo ~/shapes$ mojo run main.mojo 9.0 12.56636
from geometry import square, circle_area は、geometry.mojo から2つの関数を取り込む文です。geometry はファイル名から .mojo を除いたものです。Python の from geometry import square, circle_area と書き方がまったく同じです。
実行すると、main.mojo が読み込まれ、そこから geometry.mojo も一緒にコンパイルされて、9.0 と 12.56636 が表示されました。geometry.mojo には main がなくてもかまいません。実行の入口は、mojo run に渡したファイルの main です。
Python と同じように、import のしかたはほかにもあります。
import geometry
from geometry import circle_area as area
def main():
print(geometry.square(4.0))
print(area(1.0))~/shapes$ mojo run main2.mojo 16.0 3.14159
import geometry のあとは geometry.square のようにモジュール名を付けて呼び、as を使うと別の名前を付けられます。
import でつまずきやすい点
存在しない名前を取り込もうとした場合
from geometry import cube
def main():
print(cube(2.0))~/shapes$ mojo run main3.mojo /home/user/shapes/main3.mojo:1:22: error: module 'geometry' does not contain 'cube' from geometry import cube ^ mojo: error: failed to parse the provided Mojo source module
「モジュール geometry に cube は含まれていない」というエラーです。import の行は、プログラム全体のコンパイルの最初の段階で調べられるので、main の中のコードは1行も実行されません。
別のフォルダーからは見つからない場合
import で探してもらえるのは、実行するファイルと同じフォルダー のモジュールです。別のフォルダーに置いたファイルから geometry を取り込もうとすると、どうなるでしょうか。
from geometry import square
def main():
print(square(2.0))~/shapes$ cd ../other ~/other$ mojo run app.mojo /home/user/other/app.mojo:1:6: error: unable to locate module 'geometry' from geometry import square ^ mojo: error: failed to parse the provided Mojo source module
「モジュール geometry が見つからない」というエラーになりました。モジュールを探す場所を足したいときは、-I オプションでフォルダーを教えます。
~/other$ mojo run -I ../shapes app.mojo 4.0
今度は 4.0 が表示されました。-I ../shapes は「モジュールを探すフォルダーに ../shapes を加える」という意味です。
2.5標準ライブラリを import する
Mojo には、はじめから付いてくる 標準ライブラリ があります。print や Int、range のように、どこでも使えるものは何も書かなくても使えます。それ以外の部品は、import で取り込みます。
たとえば平方根の sqrt と円周率 pi は、math モジュールにあります。Python と同じように from math import sqrt と書いてみましょう。まず、作業場所を 1 つ上のフォルダーに戻します。
~/other$ cd ..from math import sqrt
def main():
print(sqrt(2.0))~$ mojo run stdlib_bad.mojo /home/user/stdlib_bad.mojo:1:6: error: unable to locate module 'math' from math import sqrt ^ mojo: error: failed to parse the provided Mojo source module
math というモジュールは見つからない、と言われました。Mojo 1.2.0 では、標準ライブラリのモジュールの名前は std. で始まります。math ではなく std.math と書きます。
from std.math import sqrt, pi
def main():
var r = 2.0
print("area:", pi * r * r)
print("sqrt:", sqrt(r))~$ mojo run stdlib_ok.mojo area: 12.566370614359172 sqrt: 1.4142135623730951
pi * r * r で円の面積が、sqrt(r) で 2 の平方根が求まりました。import std.math と書いて、std.math.sqrt(2.0) のように使うこともできます。
関数を使う前に import を忘れた場合は、親切なメッセージが出ます。
def main():
print(sqrt(2.0))~$ mojo run stdlib_forgot.mojo /home/user/stdlib_forgot.mojo:2:11: error: use of unknown declaration 'sqrt'; did you mean to import it from 'std.math'? Add 'from std.math import sqrt' print(sqrt(2.0)) ^~~~ mojo: error: failed to parse the provided Mojo source module
エラーの中に「from std.math import sqrt を追加してください」と、直し方がそのまま書かれています。Mojo のエラーメッセージは、import の候補まで教えてくれることがよくあります。
2.6mojo format で書式をそろえる
Python には、コードの書式をそろえる Black や ruff format のような道具があります。Mojo には、最初から mojo format コマンドが付いています。わざと書式を乱したファイルで試してみましょう。
def add(a:Int,b:Int)->Int:
return a+b
def main():
var x=add( 1,2 )
print( "x =",x )
var longname = add(add(add(1,2),add(3,4)),add(add(5,6),add(7,8)))+add(add(1,2),add(3,4))+add(add(5,6),add(7,8))
print(longname)空白の入れ方がばらばらで、関数の間に空行がなく、最後の変数の行はとても長くなっています。それでも、内容としては正しいプログラムです。
~$ mojo run messy.mojo x = 3 72
~$ mojo format messy.mojo reformatted messy.mojo All done! ✨ 🍰 ✨ 1 file reformatted. ~$ cat messy.mojo def add(a: Int, b: Int) -> Int: return a + b def main(): var x = add(1, 2) print("x =", x) var longname = ( add(add(add(1, 2), add(3, 4)), add(add(5, 6), add(7, 8))) + add(add(1, 2), add(3, 4)) + add(add(5, 6), add(7, 8)) ) print(longname) ~$ mojo run messy.mojo x = 3 72
mojo format は、ファイルを 直接書き換えます。整形のあとの cat で見えるとおり、次のように直りました。
- 引数の
a:Intがa: Intに、->の前後に空白が入った =、+、,の前後の空白がそろった- 関数の間に空行が2行入った
- 長すぎた1行が、
(と)で囲まれて複数行に分けられた
整形のあとも、実行結果は変わりません。書式が変わるだけで、プログラムの意味は変わらないのが mojo format の約束です。
1行の長さの上限は、既定では80文字です。-l(--line-length)で変えられます。
def main():
print("a very long line of text number one", "and another long text two", "three")~$ mojo format -l 40 wide.mojo reformatted wide.mojo All done! ✨ 🍰 ✨ 1 file reformatted. ~$ cat wide.mojo def main(): print( ( "a very long line of text" " number one" ), "and another long text two", "three", )
2.7コンパイル時のエラーと実行時のエラー
Mojo のエラーは、起きる時点によって2種類に分けられます。この違いを意識すると、エラーメッセージの意味が分かりやすくなります。
| 種類 | いつ起きるか | プログラムは動き出すか |
|---|---|---|
| コンパイル時のエラー | 実行の前(コンパイル中) | 動かない。何も実行されない |
| 実行時のエラー | 実行中 | 途中まで動いてから止まる |
第1章で見た、名前の打ち間違いや型の食い違いは、どれもコンパイル時のエラーでした。では、実行時のエラーとはどのようなものでしょうか。
実行時のエラー:raise されたエラー
プログラムの途中で、条件に合わないことが起きたとき、raise でエラーを発生させられます。エラーを発生させる可能性のある関数は、def の行に raises を付けて宣言します。
def check_age(age: Int) raises:
if age < 0:
raise Error("age must not be negative")
print("age is", age)
def main() raises:
print("start")
check_age(20)
check_age(-1)
print("end")check_age は、年齢が負のときにエラーを発生させます。main も、check_age を呼ぶので raises を付けています。
~$ mojo run risky.mojo start age is 20 stack trace was not collected. Enable stack trace collection with environment variable `MODULAR_DEBUG=stack-trace-on-error` Unhandled exception caught during execution: age must not be negative mojo: error: execution exited with a non-zero result: 1
出力を順に読んでみましょう。
startとage is 20が表示されました。コンパイルは通り、プログラムは 途中まで実行された ということです。check_age(-1)のところでUnhandled exception caught during execution: age must not be negativeと表示されました。「処理されなかった例外が実行中に捕まった」という意味で、続くメッセージがraiseしたときの文字列です。endは表示されていません。エラーが起きた時点でプログラムが止まったからです。- 最後の
mojo: error: execution exited with a non-zero result: 1は、mojo runが「実行したプログラムが 0 以外の結果で終わった」と伝えています。
stack trace was not collected... の行は、「エラーが起きた場所の呼び出しの履歴を取る機能は、環境変数で有効にしない限り働かない」という案内です。
終了コードを確かめる
プログラムは、終わるときに 終了コード(exit code)という数を OS に返します。0 が正常終了で、0 以外は異常終了を表します。直前のコマンドの終了コードは、bash では $? に入っています。
実行ファイルを作って、確かめてみましょう。
~$ mojo build risky.mojo ~$ ./risky; echo "終了コード: $?" start age is 20 stack trace was not collected. Enable stack trace collection with environment variable `MODULAR_DEBUG=stack-trace-on-error` Unhandled exception caught during execution: age must not be negative 終了コード: 1
./risky を直接動かしたときも、同じ Unhandled exception のメッセージが出て、終了コードは 1 でした。正常に終わったプログラムなら 0 です。
def main():
print("ok")~$ mojo run ok.mojo; echo "終了コード: $?" ok 終了コード: 0
終了コードは、シェルスクリプトや CI(自動テスト)が「このプログラムは成功したか」を判断するときに使います。
raises を付け忘れると:コンパイル時のエラー
main から raises を外すと、どうなるでしょうか。
def check_age(age: Int) raises:
if age < 0:
raise Error("age must not be negative")
def main():
print("start")
check_age(-1)~$ mojo run noraises.mojo; echo "終了コード: $?" /home/user/noraises.mojo:8:14: error: cannot call function that may raise in a context that cannot raise check_age(-1) ~~~~~~~~~^~~~ /home/user/noraises.mojo:8:14: note: try surrounding the call in a 'try' block check_age(-1) ^ /home/user/noraises.mojo:6:5: note: or mark surrounding function as 'raises' def main(): ^ mojo: error: failed to parse the provided Mojo source module 終了コード: 1
今度は、start さえ表示されませんでした。「エラーを発生させうる関数を、エラーを発生させない文脈から呼んではいけない」というコンパイル時のエラーです。メッセージの最後の2つの note は、直し方の候補で、「try で囲む」か「外側の関数に raises を付ける」の2つを教えてくれています。
raises の有無が型チェックの対象なので、「この関数はエラーを起こしうる」ということが、関数の見た目から分かります。Python では、どの関数が例外を投げるかは、実際に動かすかドキュメントを読むまで分かりませんでした。
エラーを自分で受け止める try / except は、次のように書きます。詳しくは第9章で学びます。
def check_age(age: Int) raises:
if age < 0:
raise Error("age must not be negative")
def main():
try:
check_age(-1)
except e:
print("caught:", e)
print("done")~$ mojo run caught.mojo; echo "終了コード: $?" caught: age must not be negative done 終了コード: 0
エラーを except で受け止めたので、プログラムは最後まで動き、done が表示され、終了コードは 0 になりました。main に raises を付けなくてよいのは、main の中でエラーをすべて処理しているからです。
2.8mojo のサブコマンドを知る
最後に、mojo コマンドそのものを見ておきましょう。--help を付けると、使い方の説明が出ます。
~$ mojo --help | sed -n '/^COMMANDS/,/^OPTIONS/p' COMMANDS run — Builds and executes a Mojo file. build — Builds an executable from a Mojo file. repl — Launches the Mojo REPL. debug — Launches the Mojo debugger using the command-line interface or an external editor. precompile — Precompiles a Mojo package. format — Formats Mojo source files. doc — Compiles docstrings from a Mojo file. demangle — Demangles the given name. OPTIONS
これが、mojo のサブコマンドの一覧です。この章で使ったものを中心に、役割をまとめます。
| サブコマンド | 役割 | 本書での扱い |
|---|---|---|
run | コンパイルしてすぐ実行する(省略可) | 第1章・この章 |
build | 実行ファイルなどを作る | 第1章・この章 |
format | ソースの書式をそろえる | この章 |
repl | 対話的に Mojo を試す | 本書では使わない |
debug | デバッガーで動かす | 第17章 |
doc | ドキュメント文字列を取り出す | この章 |
precompile | パッケージを事前にコンパイルする | 第18章 |
demangle | コンパイルで変わった名前を元に戻す | 本書では使わない |
説明にある run の省略は、第1章でも見たとおりです。mojo hello.mojo と書くだけで mojo run hello.mojo になります。引数なしの mojo は、repl を起動します。
各サブコマンドのくわしい使い方は、サブコマンドのあとに --help を付けると表示されます。たとえば mojo build --help を見ると、ここまで使ってきた -o(出力ファイル名)や -I(モジュールを探す場所)のほかに、次のようなオプションがあることが分かります。
~$ mojo build --help | grep -E '^ (-o|-I|--emit|--optimization-level|--debug-level)' -o <PATH> --emit <FILE_TYPE> --optimization-level <LEVEL>, -O, --no-optimization (LEVEL=0) -I <PATH> --debug-level <LEVEL>, -g (LEVEL=full), -g0 (LEVEL=none), -g1 (LEVEL=line-tables), -g2 (LEVEL=full)
--optimization-level(-O)は、最適化の強さを 0〜3 で指定するオプションです。既定は最も強い 3 で、練習問題 3 で試します。
mojo doc は、ドキュメント文字列を JSON として書き出します。この章の circle.mojo で試してみましょう。
~$ mojo doc circle.mojo -o circle.json ~$ grep -E '"name"|"summary"' circle.json | head -n 8 "name": "square", "name": "x", "name": "square", "summary": "Return x multiplied by itself." "name": "circle_area", "name": "radius", "name": "circle_area", "summary": "Return the area of a circle with the given radius."
関数の名前と、さきほど書いた英語の要約が取り出されています。ここで日本語だけの説明を書いていたら、要約として取り出せなかったでしょう。ドキュメント文字列を英語で書く理由は、こうした道具との相性にもあります。
2.9Python との違いの早見表
| 項目 | Python | Mojo |
|---|---|---|
| 実行の前に行うこと | バイトコードに変換(解釈して実行) | 機械語までコンパイル |
| 実行ファイル | ふつうは作らない | mojo build で作れる |
| 実行の入口 | ファイルの先頭から | main 関数 |
| 関数の定義順 | 呼ぶ前に定義する | 順序は関係ない |
| 自作モジュール | from geometry import square | 同じ(同じフォルダーのファイル) |
| 標準ライブラリ | from math import sqrt | from std.math import sqrt |
| 書式の整形 | Black や ruff などを別に入れる | mojo format が付属 |
| 例外を起こす関数 | 見た目では分からない | raises を明記する |
| 未処理の例外 | トレースバックを出して終了コード 1 | メッセージを出して終了コード 1 |
型の誤り・raises の付け忘れ | 実行して初めて分かる | 実行の前に分かる |
2.10まとめ
- Mojo は、実行の前にプログラム全体を、構文解析・型チェック、MLIR、LLVM IR を経て機械語にコンパイルします。
mojo runはコンパイルと実行の合計の時間、mojo buildで作った実行ファイルは実行だけの時間がかかります。速さを測るときはmojo buildしてから測ります。- プログラムファイルは、ドキュメント文字列、補助的な関数、
main関数で組み立てます。#はコメントです。ドキュメント文字列の要約は、大文字で始めてピリオドで終えます。 - 同じフォルダーのファイルは
from モジュール名 import 名前で取り込めます。別のフォルダーのモジュールは-Iで探す場所を足します。 - 標準ライブラリは
std.を付けて取り込みます(from std.math import sqrt)。 mojo formatは書式をそろえます。ファイルを直接書き換え、既定の1行は80文字までです。- 型や文法の誤りはコンパイル時に、
raiseされたエラーは実行時に見つかります。未処理のエラーで止まったプログラムの終了コードは1で、$?で確かめられます。 mojoにはrun、build、format、repl、debug、doc、precompile、demangleのサブコマンドがあります。
確認クイズ
Mojo のコンパイラが、ソースコードを機械語にするまでに経る段階を順に挙げてください。
解答を見る
構文解析と型チェック、MLIR、LLVM IR、機械語の順です。型や文法の誤りは最初の段階で見つかり、エラーがあると何も実行されません。
time mojo run x.mojoとtime ./x(mojo buildで作ったもの)で、どちらが速いのはなぜですか?解答を見る
実行ファイルの
./xのほうが速いです。mojo runはコンパイルの時間を毎回含みますが、実行ファイルはコンパイルが済んでいて、プログラム本来の実行時間だけだからです。from math import sqrtと書くとどうなりますか? どう直しますか?解答を見る
Mojo 1.2.0 では
mathというモジュールが見つからないというエラーになります。標準ライブラリのモジュール名はstd.で始まるので、from std.math import sqrtと書きます。別のフォルダーにある
geometry.mojoを取り込むには、どうすればよいですか?解答を見る
importは実行するファイルと同じフォルダーを探します。別のフォルダーのモジュールは、mojo run -I ../shapes app.mojoのように-Iで探す場所を足します。未処理のエラーで止まったプログラムの終了コードは何ですか? どうやって確かめますか?
解答を見る
1です。bash では、プログラムを実行した直後にecho $?で確かめられます。正常に終わったプログラムは0を返します。
練習問題 自分のモジュールを作る
greet.mojo に、名前(String)を受け取って "Hello, 名前!" を返す greet 関数を書いてください。それを app.mojo から import して、greet("Mojo") の結果を表示してください。
解答例を見る
2つのファイルを同じフォルダーに置きます。
# greet.mojo
def greet(name: String) -> String:
return "Hello, " + name + "!"# app.mojo
from greet import greet
def main():
print(greet("Mojo"))$ mojo run app.mojo Hello, Mojo!
文字列は Python と同じ + でつなげます。
練習問題 エラーを受け止める
この章の risky.mojo を、check_age(-1) のエラーを try / except で受け止めて age error と表示し、最後の end まで実行して、終了コードが 0 になるように書き換えてください。
解答例を見る
caught.mojo と同じ形にします。
def check_age(age: Int) raises:
if age < 0:
raise Error("age must not be negative")
print("age is", age)
def main() raises:
print("start")
check_age(20)
try:
check_age(-1)
except e:
print("age error")
print("end")check_age(20) は try の外にあるので、main の raises は残します(check_age(20) も try の中に入れれば外せます)。実行すると start、age is 20、age error、end の順に表示され、終了コードは 0 になります。
練習問題 最適化の強さを変えてみる
この章の sumloop.mojo を、-O0(最適化なし)と、既定(-O3)の2通りでビルドして、実行時間を比べてください。
解答例を見る
-o で出力ファイル名を変えて、2つビルドします。
$ mojo build -O0 sumloop.mojo -o slow $ time ./slow 2999999997 real 0m4.722s $ mojo build sumloop.mojo $ time ./sumloop 2999999997 real 0m1.115s
答えは同じ 2999999997 ですが、最適化なしのほうが数倍遅くなります。数字は環境によって変わりますが、「同じソースコードでも、最適化の強さで実行時間が大きく変わる」ことが分かります。Mojo の既定が最も強い最適化になっているのは、この速さのためです。