Mojo 入門

第2章

Mojo のプログラムはどう動くか

ソースコードが機械語になって動くまでの流れをつかみ、run と build の時間を測ります。プログラムファイルの組み立て方、ファイルの分割、標準ライブラリの読み込み、mojo format、エラーの種類、mojo のサブコマンドを学びます。

この章の内容
  1. 2.1ソースコードが機械語になるまで
  2. 2.2mojo run と mojo build の時間を測る
  3. 2.3プログラムファイルの組み立て
  4. 2.4ファイルを分けて import する
  5. 2.5標準ライブラリを import する
  6. 2.6mojo format で書式をそろえる
  7. 2.7コンパイル時のエラーと実行時のエラー
  8. 2.8mojo のサブコマンドを知る
  9. 2.9Python との違いの早見表
  10. 2.10まとめ

第1章では、Mojo を入れて最初のプログラムを動かしました。この章では、mojo run を押した裏側で何が起きているのかを知り、プログラムをファイルとして組み立てるときの基本を身につけます。

この章を終えると、次のことができるようになります。

この章から、コマンドは pixi shell の中で実行する前提で、pixi run を付けずに mojo ... とだけ書きます。

2.1ソースコードが機械語になるまで

Python では、python hello.py と打つと、インタープリターがファイルを読み、1行ずつ解釈しながら実行しました。Mojo はまったく違います。実行の前に、ファイル全体を コンパイル して、CPU がそのまま実行できる 機械語 に翻訳します。

Mojo のソースコードが実行されるまでの流れ コンパイラの中の流れ x.mojo 構文解析 型チェック MLIR LLVM IR 機械語 実行 CPU が動かす 文法と型の 誤りはここで発見 Mojo 向けの 中間表現・最適化 LLVM の 共通の中間表現 CPU ごとの 命令に変換 ここまでが「コンパイル」 mojo run:機械語をそのまま実行して終わり(ファイルは残らない) mojo build:機械語をリンクして実行ファイルとして保存(以後は実行だけ)
図2-1 Mojo のソースコードが実行されるまでの流れ

コンパイラの中では、おおまかに次の段階を順にたどります。

  1. 構文解析と型チェック:ソースコードを読んで、文法の誤りや型の食い違いがないかを調べます。第1章で見たエラーは、すべてこの段階で見つかったものです。
  2. MLIR:問題がなければ、Mojo 向けの中間表現(MLIR)に変換します。ここで Mojo 独自の最適化が行われます。
  3. LLVM IR:さらに、LLVM という共通の基盤が使う中間表現に変換します。C++ や Rust のコンパイラも同じ LLVM を使っています。ここで、LLVM が長年育ててきた最適化が適用されます。
  4. 機械語:最後に、その CPU 向けの命令に変換します。

この4段階の中身を理解する必要はありません。大切なのは「実行の前に、プログラム全体が機械語まで翻訳される」という点です。Python のように、実行しながら解釈することはありません。

中間の結果をのぞいてみる

mojo build には、途中の段階の結果を取り出すオプション --emit があります。短い関数のあるプログラムで試してみましょう。

add.mojo
def add(a: Int, b: Int) -> Int:
    return a + b


def main():
    print(add(2, 3))
Ubuntu
~$ 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 で割った余りを、すべて足し合わせるプログラムです。

sumloop.mojo
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 コマンドを前に付けると、実行にかかった時間が表示されます。

Ubuntu
~$ 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 秒かかりました。

次に、ビルドして、できた実行ファイルを測ります。

Ubuntu
~$ 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 プログラムでも比べてみましょう。

hello.mojo
def main():
    print("Hello")
Ubuntu
~$ 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 のプログラムファイルは、ふつう次の部品でできています。

circle.mojo
"""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))
Ubuntu
~$ mojo run circle.mojo
radius: 2.0
area: 12.56636

上から順に見ていきましょう。

出力を読むと、radius: 2.0、area: 12.56636 と表示されています。2.0 * 2.0 * 3.14159 が 12.56636 です。

ドキュメント文字列の書き方に決まりがある

Mojo のコンパイラは、ドキュメント文字列の書き方もチェックします。日本語で説明を書いてみましょう。

jpdoc.mojo
"""テスト用のモジュール。"""


def double(x: Int) -> Int:
    """x を 2 倍にして返す。"""
    return x * 2


def main():
    print(double(21))
Ubuntu
~$ 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 に移してみましょう。

shapes/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 を置きます。

shapes/main.mojo
from geometry import square, circle_area


def main():
    print(square(3.0))
    print(circle_area(2.0))
Ubuntu
~$ 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 のしかたはほかにもあります。

main2.mojo
import geometry
from geometry import circle_area as area


def main():
    print(geometry.square(4.0))
    print(area(1.0))
Ubuntu
~/shapes$ mojo run main2.mojo
16.0
3.14159

import geometry のあとは geometry.square のようにモジュール名を付けて呼び、as を使うと別の名前を付けられます。

import でつまずきやすい点

存在しない名前を取り込もうとした場合

main3.mojo
from geometry import cube


def main():
    print(cube(2.0))
Ubuntu
~/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 を取り込もうとすると、どうなるでしょうか。

../other/app.mojo
from geometry import square


def main():
    print(square(2.0))
Ubuntu
~/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 オプションでフォルダーを教えます。

Ubuntu
~/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 つ上のフォルダーに戻します。

Ubuntu
~/other$ cd ..
stdlib_bad.mojo
from math import sqrt


def main():
    print(sqrt(2.0))
Ubuntu
~$ 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 と書きます。

stdlib_ok.mojo
from std.math import sqrt, pi


def main():
    var r = 2.0
    print("area:", pi * r * r)
    print("sqrt:", sqrt(r))
Ubuntu
~$ 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 を忘れた場合は、親切なメッセージが出ます。

stdlib_forgot.mojo
def main():
    print(sqrt(2.0))
Ubuntu
~$ 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 コマンドが付いています。わざと書式を乱したファイルで試してみましょう。

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)

空白の入れ方がばらばらで、関数の間に空行がなく、最後の変数の行はとても長くなっています。それでも、内容としては正しいプログラムです。

Ubuntu
~$ mojo run messy.mojo
x = 3
72
Ubuntu
~$ 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 で見えるとおり、次のように直りました。

整形のあとも、実行結果は変わりません。書式が変わるだけで、プログラムの意味は変わらないのが mojo format の約束です。

1行の長さの上限は、既定では80文字です。-l(--line-length)で変えられます。

wide.mojo
def main():
    print("a very long line of text number one", "and another long text two", "three")
Ubuntu
~$ 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 を付けて宣言します。

risky.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)
    check_age(-1)
    print("end")

check_age は、年齢が負のときにエラーを発生させます。main も、check_age を呼ぶので raises を付けています。

Ubuntu
~$ 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

出力を順に読んでみましょう。

stack trace was not collected... の行は、「エラーが起きた場所の呼び出しの履歴を取る機能は、環境変数で有効にしない限り働かない」という案内です。

終了コードを確かめる

プログラムは、終わるときに 終了コード(exit code)という数を OS に返します。0 が正常終了で、0 以外は異常終了を表します。直前のコマンドの終了コードは、bash では $? に入っています。

実行ファイルを作って、確かめてみましょう。

Ubuntu
~$ 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 です。

ok.mojo
def main():
    print("ok")
Ubuntu
~$ mojo run ok.mojo; echo "終了コード: $?"
ok
終了コード: 0

終了コードは、シェルスクリプトや CI(自動テスト)が「このプログラムは成功したか」を判断するときに使います。

raises を付け忘れると:コンパイル時のエラー

main から raises を外すと、どうなるでしょうか。

noraises.mojo
def check_age(age: Int) raises:
    if age < 0:
        raise Error("age must not be negative")


def main():
    print("start")
    check_age(-1)
Ubuntu
~$ 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章で学びます。

caught.mojo
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")
Ubuntu
~$ mojo run caught.mojo; echo "終了コード: $?"
caught: age must not be negative
done
終了コード: 0

エラーを except で受け止めたので、プログラムは最後まで動き、done が表示され、終了コードは 0 になりました。main に raises を付けなくてよいのは、main の中でエラーをすべて処理しているからです。

2.8mojo のサブコマンドを知る

最後に、mojo コマンドそのものを見ておきましょう。--help を付けると、使い方の説明が出ます。

Ubuntu
~$ 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(モジュールを探す場所)のほかに、次のようなオプションがあることが分かります。

Ubuntu
~$ 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 で試してみましょう。

Ubuntu
~$ 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 との違いの早見表

項目PythonMojo
実行の前に行うことバイトコードに変換(解釈して実行)機械語までコンパイル
実行ファイルふつうは作らないmojo build で作れる
実行の入口ファイルの先頭からmain 関数
関数の定義順呼ぶ前に定義する順序は関係ない
自作モジュールfrom geometry import square同じ(同じフォルダーのファイル)
標準ライブラリfrom math import sqrtfrom std.math import sqrt
書式の整形Black や ruff などを別に入れるmojo format が付属
例外を起こす関数見た目では分からないraises を明記する
未処理の例外トレースバックを出して終了コード 1メッセージを出して終了コード 1
型の誤り・raises の付け忘れ実行して初めて分かる実行の前に分かる

2.10まとめ

確認クイズ

  1. Mojo のコンパイラが、ソースコードを機械語にするまでに経る段階を順に挙げてください。

    解答を見る

    構文解析と型チェック、MLIR、LLVM IR、機械語の順です。型や文法の誤りは最初の段階で見つかり、エラーがあると何も実行されません。

  2. time mojo run x.mojo と time ./x(mojo build で作ったもの)で、どちらが速いのはなぜですか?

    解答を見る

    実行ファイルの ./x のほうが速いです。mojo run はコンパイルの時間を毎回含みますが、実行ファイルはコンパイルが済んでいて、プログラム本来の実行時間だけだからです。

  3. from math import sqrt と書くとどうなりますか? どう直しますか?

    解答を見る

    Mojo 1.2.0 では math というモジュールが見つからないというエラーになります。標準ライブラリのモジュール名は std. で始まるので、from std.math import sqrt と書きます。

  4. 別のフォルダーにある geometry.mojo を取り込むには、どうすればよいですか?

    解答を見る

    import は実行するファイルと同じフォルダーを探します。別のフォルダーのモジュールは、mojo run -I ../shapes app.mojo のように -I で探す場所を足します。

  5. 未処理のエラーで止まったプログラムの終了コードは何ですか? どうやって確かめますか?

    解答を見る

    1 です。bash では、プログラムを実行した直後に echo $? で確かめられます。正常に終わったプログラムは 0 を返します。

練習問題 自分のモジュールを作る

greet.mojo に、名前(String)を受け取って "Hello, 名前!" を返す greet 関数を書いてください。それを app.mojo から import して、greet("Mojo") の結果を表示してください。

解答例を見る

2つのファイルを同じフォルダーに置きます。

mojo
# greet.mojo
def greet(name: String) -> String:
    return "Hello, " + name + "!"
mojo
# app.mojo
from greet import greet


def main():
    print(greet("Mojo"))
Ubuntu
$ mojo run app.mojo
Hello, Mojo!

文字列は Python と同じ + でつなげます。

練習問題 エラーを受け止める

この章の risky.mojo を、check_age(-1) のエラーを try / except で受け止めて age error と表示し、最後の end まで実行して、終了コードが 0 になるように書き換えてください。

解答例を見る

caught.mojo と同じ形にします。

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つビルドします。

Ubuntu
$ 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 の既定が最も強い最適化になっているのは、この速さのためです。