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

【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)

Disappointed
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!
Result

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