2026-03-04 06:43:10
諏訪子
odin
lowlevel

【Odin】Odin言語のレビュー(更新済み)

此れまでC言語やC++の代替を目指した言語が沢山登場してきました。
其れでも尚、C言語とC++は世界で最も使われている言語の2位や3位に残り続け、寧ろ利用も増えています。
PythonだけがCとC++を上回っている様です。
此れには非常にちゃんとした理由があります。
C言語とC++は単なるプログラミング言語であって、エコシステム全体ではないからです。

システムプログラミング言語についてあたしが奇妙に思うのは、其れらをペアで並べると其々の主な売りポイントがはっきり見える事です。

  • 低レベルかつ多機能:C言語とC++
  • メモリ安全性主張:ZigとRust
  • Apple中心:Objective-CとSwift
  • Google中心若しくはWeb開発寄り:Go言語とCarbon
  • ゲーム開発特化:OdinとJai

此れらをグループ化したのは、文法が似ている(C言語とC++、ZigとRust、OdinとJai)か、同じ企業出身(Objective-CとSwift、Go言語とCarbon)だからです。
全部試したわけではないですが、試した物の中で:

  • C言語はほぼ何でも、特に組み込み、TUI、デスクトップGUI、CLIプログラムであたしの第一選択言語です。
  • C++はゲーム開発のメイン言語ですが、Cでは足りない時に他の用途でも使います。
  • Go言語はサーバーデーモンのメイン言語でしたが、Web開発を辞めてからはもう使っていません。
  • Zigは一時期好きになろうと頑張った言語です。良い点は多いのですが、極端にドグマティックで、ABIが非常に不安定なので、コンパイラや自動フォーマッタと戦う時間の方が作業時間より多くなり、使うのを辞めました。

コードを見た瞬間に諦めた言語:

  • Rustの文法があんまにも暗号的過ぎて、いったい何をやっているのかどう頑張っても理解出来ない。
  • Objective-Cの文法が酷過ぎて、見るだけで脳が死ぬ。

その他の理由で使っていない言語:

  • SwiftはAppleエコシステムに縛られていた(MacBook1台は遅すぎ、もう1台は死んでいる)。今はWindowsやLinuxでも動くが、EULAが怖い。
  • Carbonは4年経っても未だ0.0.0で、全然良い兆候ではない。Google社はいったい何を4年間やってたんだ?オナニーか?しかもソースからビルドが必要で、Googleのコードをソースからビルドする辛さを知っているので試す気にもならない。
  • Jaiは未だ一般公開されていないので試せない。

勿論此れらも触れておきます:

  • PHP — 最初に触れた言語で、此のブログも此れで書かれている。でもシステム言語ではない。
  • アセンブリ言語 — 此れらの言語が本当の姿で隠れている物。主な用途はコード最適化、コンパイラ、ゲームボーイやスーパーファミコンでのプログラミング。

未だ触れていない言語が一つあります:Odin。
理由は、此れだけが「未だ使っていないけど使える」言語だからです。
此処でOdinをレビューし、必要に応じてC言語、C++、Go言語、Zigと比較します。

Odinのメリット

Odinはシンプルさ、高性能、手動メモリ管理とカスタムアロケータ、プログラミングの楽しさ、ドグマフリー、充実した標準ライブラリ、パッケージマネージャなし、LSPなし(一応はある)、高速コンパイルに焦点を当てている様です。
非常に良い売り文句です。
特に目立つのは、ゲーム開発者向けにかなり調整されている点です。
有名なC言語/C++ライブラリが標準ライブラリに含まれるのは素晴らしい。

但し標準ライブラリは3つに分かれているようです:basecorevendor
baseは超基本的な物、coreはGo言語が含む物+α、vendorはゲーム開発のよく使われるライブラリ群。

インストール

Odinはあたしが使うほぼ全てのOSとアーキテクチャに対応しています(RISC-Vだけ欠けている)。
まるであたしが作った言語みたいです。

問題:ドキュメント

始める前から最初の問題にぶち当たりました:ドキュメントの不足。
ドキュメント不足はZigをダメにした大きな要因の一つでした(他にも問題は山程ありますが)。
但しウェブサイトに沢山のサンプルがある様なので、あたしには其れで十分です。

こんにちは、世界!

全てのプログラミング言語が最初にやる事:こんにちは、世界!

package main

import "core:fmt"

main :: proc() {
  fmt.println("こんにちは、世界!")
}

実行:

$ odin run .
Error: Try using '-microarch:native' as Odin defaults to x86-64-v2 (close to Nehalem), and your CPU seems to be older.

マジかよ?
Core 2 DuoにOdinがない?
最悪だわ。

更新(2026年03月04日)
このポスト によると、Core 2 Duo でのコンパイルが可能との事です。

$ odin run . -microarch:x86-64
こんにちは、世界!

何故デフォルトで古いプロセッサをサポートしないのか理解出来ませんが、回避策があると分かって良かったです。

ではRaspberry Pi 4で試してみましょう。

$ odin run .
こんにちは、世界!

疑う余地なく、此れが一番Go言語に似ています。
パッケージ名を定義する必要がある、importするのは"fmt"、mainに関数型がない、セミコロンなし、printlnがfmtの一部。
Go言語での同一プログラム:

package main

import "fmt"

func main() {
  fmt.Println("こんにちは、世界!")
}

因みにGo言語やZigみたいにデフォルトで静的リンクしているのかな?

$ odin build .
$ ls -thal 01.hello
-rwxr-xr-x  1 suwako suwako  362K  3月  4 02:24 01.hello
$ file 01.hello
01.hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1,
for FreeBSD 15.0 (1500068), FreeBSD-style, with debug_info, not stripped
$ ldd 01.hello
01.hello:
        libm.so.5 => /lib/libm.so.5 (0xd45ee190000)
        libc.so.7 => /lib/libc.so.7 (0xd45e9e00000)
        libsys.so.7 => /lib/libsys.so.7 (0xd45fd000000)

残念
めっちゃガッカリ・・・

更新(2026年03月04日)
このポスト によると、静的リンクが可能との事です。

$ odin build . -extra-linker-flags:-static
$ ls -thal 01.hello
-rwxr-xr-x  1 suwako suwako  4.9M  3月  4 14:04 01.hello
$ file 01.hello
01.hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (FreeBSD), statically linked,
for FreeBSD 15.0 (1500068), FreeBSD-style, with debug_info, not stripped
$ ldd 01.hello
ldd: 01.hello: not a dynamic ELF executable

デフォルトで静的リンクになっていない事実は変わりませんが、オプションがあると分かって良かったです。

アセンブリはどうなっているか見てみましょう。

$ objdump -d 01.hello | less
...
000000000023d114 <main>:
  23d114: d10583ff      sub     sp, sp, #0x160
  23d118: a9157bfd      stp     x29, x30, [sp, #0x150]
  23d11c: b90037e0      str     w0, [sp, #0x34]
  23d120: f9001fe1      str     x1, [sp, #0x38]
  23d124: 14000001      b       0x23d128 <main+0x14>
  23d128: b94037e9      ldr     w9, [sp, #0x34]
  23d12c: f9401fe8      ldr     x8, [sp, #0x38]
  23d130: b9014fe9      str     w9, [sp, #0x14c]
  23d134: f900a3e8      str     x8, [sp, #0x140]
  23d138: f940a3e8      ldr     x8, [sp, #0x140]
  23d13c: f9000be8      str     x8, [sp, #0x10]
  23d140: 2a0903e8      mov     w8, w9
  23d144: 93407d05      sxtw    x5, w8
  23d148: f90007e5      str     x5, [sp, #0x8]
  23d14c: d503201f      nop
  23d150: 30e206a8      adr     x8, 0x201225 <write+0x201225>
  23d154: f90093e8      str     x8, [sp, #0x120]
  23d158: 52800608      mov     w8, #0x30               // =48
  23d15c: f90097e8      str     x8, [sp, #0x128]
  23d160: f94093e0      ldr     x0, [sp, #0x120]
  23d164: f94097e1      ldr     x1, [sp, #0x128]
  23d168: 528006c2      mov     w2, #0x36               // =54
  23d16c: 52800223      mov     w3, #0x11               // =17
  23d170: aa1f03e4      mov     x4, xzr
  23d174: 940073ce      bl      0x25a0ac <runtime::multi_pointer_slice_expr_error>
  23d178: f94007e8      ldr     x8, [sp, #0x8]
  23d17c: f9400be9      ldr     x9, [sp, #0x10]
  23d180: 91000108      add     x8, x8, #0x0
  23d184: f9009be9      str     x9, [sp, #0x130]
  23d188: f9009fe8      str     x8, [sp, #0x138]
  23d18c: f9409bea      ldr     x10, [sp, #0x130]
  23d190: f9409fe8      ldr     x8, [sp, #0x138]
  23d194: d503201f      nop
  23d198: 1022da49      adr     x9, 0x282ce0 <runtime::args__>
  23d19c: f900012a      str     x10, [x9]
  23d1a0: f9000528      str     x8, [x9, #0x8]
  23d1a4: 9102c3e0      add     x0, sp, #0xb0
  23d1a8: f90017e0      str     x0, [sp, #0x28]
  23d1ac: 52800e08      mov     w8, #0x70               // =112
  23d1b0: 2a0803e2      mov     w2, w8
  23d1b4: f90013e2      str     x2, [sp, #0x20]
  23d1b8: 2a1f03e1      mov     w1, wzr
  23d1bc: b90033e1      str     w1, [sp, #0x30]
  23d1c0: 9400940c      bl      0x2621f0 <memset@plt>
  23d1c4: f94017e0      ldr     x0, [sp, #0x28]
  23d1c8: 97ffff48      bl      0x23cee8 <runtime::[core.odin]::__init_context>
  23d1cc: f94013e2      ldr     x2, [sp, #0x20]
  23d1d0: b94033e1      ldr     w1, [sp, #0x30]
  23d1d4: 910103e0      add     x0, sp, #0x40
  23d1d8: f9000fe0      str     x0, [sp, #0x18]
  23d1dc: 94009405      bl      0x2621f0 <memset@plt>
  23d1e0: f9400fe8      ldr     x8, [sp, #0x18]
  23d1e4: 97ffff2d      bl      0x23ce98 <runtime::default_context>
  23d1e8: f9400fe1      ldr     x1, [sp, #0x18]
  23d1ec: f94013e2      ldr     x2, [sp, #0x20]
  23d1f0: f94017e0      ldr     x0, [sp, #0x28]
  23d1f4: 94009407      bl      0x262210 <memmove@plt>
  23d1f8: f94017e0      ldr     x0, [sp, #0x28]
  23d1fc: 94006ad2      bl      0x257d44 <__$startup_runtime>
  23d200: f94017e0      ldr     x0, [sp, #0x28]
  23d204: 940036da      bl      0x24ad6c <main::main>
  23d208: f94017e0      ldr     x0, [sp, #0x28]
  23d20c: 94006bb7      bl      0x2580e8 <__$cleanup_runtime>
  23d210: b94033e0      ldr     w0, [sp, #0x30]
  23d214: a9557bfd      ldp     x29, x30, [sp, #0x150]
  23d218: 910583ff      add     sp, sp, #0x160
  23d21c: d65f03c0      ret

かなり非効率に見えます。
ZigやGo言語程酷くはないけど、C言語やC++よりは明らかに悪い。
Odinは只文字を表示するだけで、裏で大量のメモリ確保や操作、エラーチェックをしている様です。
良くないですね。

更新(2026年03月04日)
このポスト によると、命令数を削減出来るとの事です。

$ odin build . -o:minimal -no-bounds-check -disable-assert
$ objdump -d 01.hello | less
000000000021df70 <main::main>:
  21df70: d10203ff      sub     sp, sp, #0x80
  21df74: f9003bfe      str     x30, [sp, #0x70]
  21df78: f90007e0      str     x0, [sp, #0x8]
  21df7c: 14000001      b       0x21df80 <main::main+0x10>
  21df80: f94007e5      ldr     x5, [sp, #0x8]
  21df84: f90033ff      str     xzr, [sp, #0x60]
  21df88: f90037ff      str     xzr, [sp, #0x68]
  21df8c: 910143e8      add     x8, sp, #0x50
  21df90: f9002bff      str     xzr, [sp, #0x50]
  21df94: f9002fff      str     xzr, [sp, #0x58]
  21df98: 910103e9      add     x9, sp, #0x40
  21df9c: d503201f      nop
  21dfa0: 30f14c0a      adr     x10, 0x200921 <write+0x200921>
  21dfa4: f90023ea      str     x10, [sp, #0x40]
  21dfa8: 5280036a      mov     w10, #0x1b              // =27
  21dfac: f90027ea      str     x10, [sp, #0x48]
  21dfb0: f9001be9      str     x9, [sp, #0x30]
  21dfb4: d2941e29      mov     x9, #0xa0f1             // =41201
  21dfb8: f2bac8a9      movk    x9, #0xd645, lsl #16
  21dfbc: f2df55a9      movk    x9, #0xfaad, lsl #32
  21dfc0: f2e543e9      movk    x9, #0x2a1f, lsl #48
  21dfc4: f9001fe9      str     x9, [sp, #0x38]
  21dfc8: f9401bea      ldr     x10, [sp, #0x30]
  21dfcc: f9401fe9      ldr     x9, [sp, #0x38]
  21dfd0: f9002bea      str     x10, [sp, #0x50]
  21dfd4: f9002fe9      str     x9, [sp, #0x58]
  21dfd8: f90033e8      str     x8, [sp, #0x60]
  21dfdc: 52800028      mov     w8, #0x1                // =1
  21dfe0: f90037e8      str     x8, [sp, #0x68]
  21dfe4: f94033ea      ldr     x10, [sp, #0x60]
  21dfe8: f94037e9      ldr     x9, [sp, #0x68]
  21dfec: f90013ea      str     x10, [sp, #0x20]
  21dff0: f90017e9      str     x9, [sp, #0x28]
  21dff4: f94013e0      ldr     x0, [sp, #0x20]
  21dff8: f94017e1      ldr     x1, [sp, #0x28]
  21dffc: f0ffff09      adrp    x9, 0x200000 <write+0x200000>
  21e000: 9124f529      add     x9, x9, #0x93d
  21e004: f9000be9      str     x9, [sp, #0x10]
  21e008: f9000fe8      str     x8, [sp, #0x18]
  21e00c: f9400be2      ldr     x2, [sp, #0x10]
  21e010: f9400fe3      ldr     x3, [sp, #0x18]
  21e014: 52800028      mov     w8, #0x1                // =1
  21e018: 12000104      and     w4, w8, #0x1
  21e01c: 9400328f      bl      0x22aa58 <fmt::println>
  21e020: f9403bfe      ldr     x30, [sp, #0x70]
  21e024: 910203ff      add     sp, sp, #0x80
  21e028: d65f03c0      ret

僅かな削減ではありますが、其れでも未だ命令数が多過ぎます。

但し後で分かったのですが、Odinは静的リンクも出来ます。

$ odin build . -build-mode:static
$ file 01.hello
01.hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1,
for FreeBSD 15.0 (1500068), FreeBSD-style, with debug_info, not stripped

・・・結局出来ないみたいです。
修正される事を祈ります。

後コンパイル時間が結構長い事に気づきました。
ZigやC++程ではないですが、C言語やGo言語よりは明らかに遅いです。
Raspberry Piでコンパイルしているからかもしんが、あんま厳しく見ないでおきます。

OpenGL

もう少し実用的な物、例えばOpenGLウィンドウの描画を試してみましょう。
どうせOdinはゲーム開発者向けに作られていると言っているので、其の期待に応えられるか確認しないと。

package main

import gl "vendor:OpenGL"
import "vendor:glfw"
import "core:fmt"
import "core:os"
import "core:c"

framebuffer_cb :: proc "c" (window: glfw.WindowHandle, width: i32, height: i32) {
  gl.Viewport(0, 0, width, height)
}

vertexSrc: cstring =
`#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
  gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}`

fragSrc: cstring =
`#version 330 core
out vec4 FragColor;
void main() {
  FragColor = vec4(1.f, .5f, .2f, 1.f);
}`

WIDTH :: 500
HEIGHT :: 300

main ::proc() {
  glfw.Init()
  glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3)
  glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3)
  glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)

  window := glfw.CreateWindow(WIDTH, HEIGHT, "例え", nil, nil)
  defer glfw.Terminate()
  if window == nil {
    fmt.println("GLFWウィンドウを開くに失敗")
    os.exit(-1)
  }
  glfw.MakeContextCurrent(window)

  gl.load_up_to(3, 3, glfw.gl_set_proc_address)
  gl.Viewport(0, 0, WIDTH, HEIGHT)
  glfw.SetFramebufferSizeCallback(window, framebuffer_cb)

  success: i32
  infoLog: [512]c.char

  vertexShader := gl.CreateShader(gl.VERTEX_SHADER)
  gl.ShaderSource(vertexShader, 1, &vertexSrc, nil)
  gl.CompileShader(vertexShader)

  gl.GetShaderiv(vertexShader, gl.COMPILE_STATUS, &success)
  if success != 1 {
    gl.GetShaderInfoLog(vertexShader, 512, nil, raw_data(&infoLog))
    os.exit(-1)
  }

  fragShader := gl.CreateShader(gl.FRAGMENT_SHADER)
  gl.ShaderSource(fragShader, 1, &fragSrc, nil)
  gl.CompileShader(fragShader)

  gl.GetShaderiv(fragShader, gl.COMPILE_STATUS, &success)
  if success != 1 {
    gl.GetShaderInfoLog(fragShader, 512, nil, raw_data(&infoLog))
    os.exit(-1)
  }

  shaderProgram := gl.CreateProgram()
  defer gl.DeleteProgram(shaderProgram)
  gl.AttachShader(shaderProgram, vertexShader)
  gl.AttachShader(shaderProgram, fragShader)
  gl.LinkProgram(shaderProgram)

  gl.GetProgramiv(fragShader, gl.LINK_STATUS, &success)
  if success != 1 {
    gl.GetShaderInfoLog(fragShader, 512, nil, raw_data(&infoLog))
    os.exit(-1)
  }

  gl.DeleteShader(vertexShader)
  gl.DeleteShader(fragShader)

  vertices := [?]f32 {
     .5,  .5, .0,
     .5, -.5, .0,
    -.5, -.5, .0,
    -.5,  .5, .0,
  }

  indices := [?]u32 {
    0, 1, 3,
    1, 2, 3,
  }

  VBO, VAO, EBO: u32
  gl.GenVertexArrays(1, &VAO)
  defer gl.DeleteVertexArrays(1, &VAO)
  gl.GenBuffers(1, &VBO)
  defer gl.DeleteBuffers(1, &VBO)
  gl.GenBuffers(1, &EBO)
  defer gl.DeleteBuffers(1, &EBO)
  gl.BindVertexArray(VAO)

  gl.BindBuffer(gl.ARRAY_BUFFER, VBO)
  gl.BufferData(gl.ARRAY_BUFFER, size_of(vertices),
                raw_data(&vertices), gl.STATIC_DRAW)

  gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO)
  gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size_of(indices),
                raw_data(&indices), gl.STATIC_DRAW)

  gl.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 3 * size_of(f32), 0)
  gl.EnableVertexAttribArray(0)

  gl.BindBuffer(gl.ARRAY_BUFFER, 0)
  gl.BindVertexArray(0)

  for !glfw.WindowShouldClose(window) {
    if glfw.GetKey(window, glfw.KEY_Q) == glfw.PRESS {
      glfw.SetWindowShouldClose(window, true)
    }

    gl.ClearColor(.6, .1, .6, 1.)
    gl.Clear(gl.COLOR_BUFFER_BIT)
    gl.UseProgram(shaderProgram)
    gl.BindVertexArray(VAO)
    gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil)

    glfw.SwapBuffers(window)
    glfw.PollEvents()
  }
}

此れはOdinレビュー記事なのでOpenGLチュートリアルではないので説明は省きます。
コンパイル:

$ odin build .
ld: error: unable to find library -lglfw
clang: error: linker command failed with exit code 1 (use -v to see invocation)

え、GLFWが言語に含まれているのに、システムにライブラリをインストールする必要があるの?
ガッカリポイントです。
まあ最悪ではないですが。

$ doas pkg install glfw
Updating FreeBSD-ports repository catalogue...
FreeBSD-ports repository is up to date.
Updating FreeBSD-ports-kmods repository catalogue...
FreeBSD-ports-kmods repository is up to date.
All repositories are up to date.
The following 1 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        glfw: 3.4_2 [FreeBSD-ports]

Number of packages to be installed: 1

The process will require 7 MiB more space.
1 MiB to be downloaded.

Proceed with this action? [y/N]: y
[1/1] Fetching glfw-3.4_2: 100%    1 MiB   1.0 M/s    00:01
Checking integrity... done (0 conflicting)
[1/1] Installing glfw-3.4_2...
[1/1] Extracting glfw-3.4_2: 100%
$ odin build .
$ ./02.opengl

動いた!
結果

只此のコードはちょっと判断が難しいです。
かなりGo言語っぽいけどZigの要素も少し混じっていて、基本的にはCコードに色々プレフィックス付けた感じ。
まあCライブラリを使っているわけですし、Zigと同じくCライブラリとの相互運用が出来ている様です。

次はVulkanをやりたい所ですが、長過ぎるしネタバレし過ぎるし時間もなくなってきたので辞めておきます。

総評

Odinを「プログラミングが楽しいC言語」だと思うか? → いいえ
Odinが近いうちにC言語を置き換えると思うか? → いいえ
OdinがZigより優れていると思うか? → はい
Odinに居場所があると思うか? → はい

あたしの見立てでは、Odinは要するに「必要なC言語/C++ライブラリが(マシンにインストールされていれば)最初から入っているGo言語」です。
言語自体は未だ開発中で、まだまだ道のりは長いです。
現段階ではC言語より優れているとは思いません。
ゲーム開発者のうち「ライブラリを手動でダウンロードしたくない人」には居場所があると思います。
其れ以外では、C言語より優先する理由がまだ見つかっていません。

以上