現在のブログ
ゲーム開発ブログ (2025年~) Gamedev Blog (2025~)
レガシーブログ
テクノロジーブログ (2018~2024年) リリースノート (2023~2025年) MeatBSD (2024年)
【Odin】Review of the Odin Language (Updated)
So far, many languages have appeared aiming to replace C or C++.
Yet C and C++ still remain ranked 2nd or 3rd among the most used languages in the world — in fact, their usage is even increasing.
It seems only Python has surpassed C and C++.
There is a very good reason for this.
C and C++ are just programming languages — they are not entire ecosystems.
What I find strange about systems programming languages is that when you pair them up, their main selling points become very clear:
- Low-level + feature-rich: C and C++
- Claims memory safety: Zig and Rust
- Apple-centric: Objective-C and Swift
- Google-centric or web-leaning: Go and Carbon
- Game-development focused: Odin and Jai
I grouped them this way because either the syntax is similar (C/C++, Zig/Rust, Odin/Jai) or they come from the same company (Objective-C/Swift, Go/Carbon).
I haven't tried all of them, but among the ones I have:
- C is almost always my first-choice language for just about everything — especially embedded, TUI, desktop GUI, CLI programs.
- C++ is my main language for game development, but I also use it for other things when C isn't enough.
- Go used to be my main language for server daemons, but after I stopped doing web development I no longer use it.
- Zig — I once really tried hard to like it. It has many good points, but it's extremely dogmatic and the ABI is very unstable, so I ended up spending more time fighting the compiler and auto-formatter than actually working. I gave up.
Languages I gave up on the moment I saw the code:
- Rust's syntax is so cryptographic that no matter how hard I try I can't understand what the fuck is going on.
- Objective-C's syntax is so awful that just looking at it kills my brain.
Languages I'm not using for other reasons:
- Swift is tied to the Apple ecosystem (one MacBook is too slow, the other is dead). It now runs on Windows and Linux too, but the EULA scares me.
- Carbon is still at 0.0.0 after four years — not a good sign at all. What the fuck has Google been doing for four years? Jerking off? Also it requires building from source, and knowing how painful building Google code from source is, I don't even want to try.
- Jai still isn't publicly released so I can't try it.
Of course I should mention these too:
- PHP — the first language I ever touched; this very blog is written in it. But it's not a systems language.
- Assembly — the true form hidden behind these languages. Mainly used for code optimization, compilers, Game Boy / Super Famicom programming.
There is still one language I haven't touched: Odin.
The reason is that it's the only one that is "not used yet, but usable".
So here I review Odin, and compare it with C, C++, Go, and Zig where necessary.
Advantages of Odin
Odin seems to focus on simplicity, high performance, manual memory management + custom allocators, enjoyment of programming, dogma-free, rich standard library, no package manager, no LSP (though there is one kinda), and fast compilation.
Very good selling points.
Especially notable is how heavily tuned it is toward game developers.
Having well-known C/C++ libraries included in the standard library is fantastic.
However, the standard library appears to be divided into three parts: base, core, vendor.
base = ultra basic stuff
core = roughly what Go includes + α
vendor = collection of libraries commonly used in game development
Installation
Odin supports almost every OS and architecture I use (only missing RISC-V).
It's almost like a language I would have made myself.
Problem: Documentation
Right from the start I ran into the first issue: lack of documentation.
Lack of documentation was one of the major things that killed Zig for me (among a mountain of other problems).
However, the website has lots of examples, so for me that's enough.
Hello, World!
The first thing every programming language does: Hello, World!
package main
import "core:fmt"
main :: proc() {
fmt.println("こんにちは、世界!")
}
Execution:
$ odin run .
Error: Try using '-microarch:native' as Odin defaults to x86-64-v2 (close to Nehalem), and your CPU seems to be older.
Seriously?
No Odin on Core 2 Duo?
That's gay.
Update (March 04, 2026):
According to this post, it appears that compilation on Core 2 Duo is possible.
$ odin run . -microarch:x86-64
こんにちは、世界!
I don't understand why old processors are not supported by default, but I’m glad there's a workaround.
Let's try it on Raspberry Pi 4 then.
$ odin run .
こんにちは、世界!
No doubt about it — this is the most Go-like so far.
Need to define a package name, import "fmt", no function type on main, no semicolons, println is part of fmt.
The same program in Go:
package main
import "fmt"
func main() {
fmt.Println("こんにちは、世界!")
}
By the way, is it statically linked by default like Go and 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)

Super disappointed...
Update (March 04, 2026):
According to this post, static linking is possible.
$ 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
The fact that it's not statically linked by default remains unchanged, but I’m glad there's at least an option.
Let's look at the assembly.
$ 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
Looks quite inefficient.
Not as bad as Zig or Go, but clearly worse than C or C++.
Just to print some text, it seems to be doing a ton of memory allocation / manipulation / error checking behind the scenes.
Not good.
Update (March 04, 2026):
According to this post, the instruction count can be reduced.
$ 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
It’s only a small reduction, but still — the instruction count is way too high even after that.
But later I found out that Odin can do static linking.
$ 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
...looks like it actually can't.
I hope this gets fixed.
Also noticed that compile times are quite long.
Not as bad as Zig or C++, but clearly slower than C or Go.
Maybe because I'm compiling on a Raspberry Pi, so I won't judge too harshly.
OpenGL
Let's try something a bit more practical — drawing an OpenGL window.
Since Odin claims to be made for game developers, I need to check whether it lives up to that expectation.
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()
}
}
This is an Odin review article, not an OpenGL tutorial, so I'll skip the explanation.
Compile:
$ odin build .
ld: error: unable to find library -lglfw
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Wait — even though GLFW is included in the language, I still need to install the library on the system?
Disappointing point.
Well, not the worst thing ever.
$ 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
It worked!
This code is a bit hard to judge.
Very Go-like, but also has some Zig flavor, and basically feels like C code with a bunch of prefixes added.
Well, since it's using C libraries, it seems to have good C interop just like Zig.
I'd like to try Vulkan next, but it would be too long, too spoilery, and I'm running out of time, so I'll stop here.
Overall Impression
Do I think Odin is "a fun C language to program in"? → No
Do I think Odin will replace C anytime soon? → No
Do I think Odin is better than Zig? → Yes
Do I think Odin has a place? → Yes
In my view, Odin is essentially "Go, but with all the necessary C/C++ libraries already included (as long as they're installed on the machine)".
The language itself is still under development and has a long way to go.
At the current stage, I don't think it's better than C.
I believe it has a niche among game developers who "don't want to manually download libraries".
For everyone else, I still don't see a reason to prefer it over C.
That's all