Odin & raylib · Lesson 01 · 15 min
Your First Window
Install the Odin toolchain and get a raylib window rendering, then learn the five language ideas that program is made of.
You came here to make games, so the first thing you do is put a window on screen. The language lesson arrives afterwards, attached to code you have already run.
There is one piece of good news that makes this faster than you expect: raylib ships inside the Odin compiler. There is no package to add, no build file, no linker flags. Install Odin and you have installed raylib.
Install the toolchain
Odin is in the Arch extra repository, so this is one command:
sudo pacman -S odin
odin version
You should see a version string like odin version dev-2026-08. Odin ships monthly
releases rather than semantic versions, and the Arch package tracks them closely.
Where raylib actually lives
The compiler installs to /usr/lib/odin/. Inside it, vendor/raylib/
holds the Odin bindings and a prebuilt libraylib.so.600 — raylib 6.0. That
directory is the reason you don't install raylib separately.
It is also the best documentation you have. raylib.odin is about 1,900
lines with a comment on every procedure, and grepping it is faster than any website:
grep -n "DrawCircle" /usr/lib/odin/vendor/raylib/raylib.odin
The whole program
package main
import rl "vendor:raylib"
main :: proc() {
rl.InitWindow(960, 540, "Hello, Odin")
defer rl.CloseWindow()
rl.SetTargetFPS(60)
for !rl.WindowShouldClose() {
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground(rl.RAYWHITE)
rl.DrawText("It renders.", 32, 32, 32, rl.DARKGRAY)
}
}
Run it
mkdir -p ~/games/hello && cd ~/games/hello
# save the program above as main.odin
odin run .
A 960×540 window opens with grey text on off-white. Close it with the window button or Esc — raylib treats Esc as "should close" by default.
Note the . in odin run .. You are not compiling a file, you are compiling a
directory. In Odin a directory is a package, every .odin file in it belongs to that
package, and there are no import statements between files in the same package. When your
game grows to eight files, you still type odin run ..
Five ideas that program is made of
1. :: binds at compile time, := binds at runtime
main :: proc() { ... } is not special function-declaration syntax. :: means this name
is a constant, and a procedure is a constant whose value happens to be a procedure. The
exact same operator declares a number:
WINDOW_WIDTH :: 960 // constant, exists only at compile time
main :: proc() { } // also a constant
frame_count := 0 // variable, exists at runtime
elapsed: f32 // variable, explicitly typed, starts at 0
Odin has two declaration operators and the difference between them is when the value exists. Coming from a higher-level language this is the single highest-leverage thing to internalise, because it explains procedures, constants, types, and imports all at once.
2. vendor: is a collection that ships with the compiler
import rl "vendor:raylib"
vendor is a named collection resolved relative to the Odin installation, alongside
core (the standard library) and base. rl is a local alias — you could call it
raylib, but rl is what every Odin codebase uses.
3. defer runs at the end of the enclosing scope
This is the idea that will change how you write cleanup code. defer schedules a
statement to run when the current scope exits, so you can write the undo next to the do:
rl.InitWindow(...)
defer rl.CloseWindow() // runs when main returns
Now look at the loop body:
for !rl.WindowShouldClose() {
rl.BeginDrawing()
defer rl.EndDrawing() // runs at the end of EVERY iteration
// ...
}
The for body is its own scope, so that defer fires once per frame. Every raylib
Begin/End pair — drawing, 2D camera mode, textures, shaders — can be written this way,
which means the pairs cannot silently drift apart as the body grows.
4. for is the only loop keyword
There is no while. A for with a single condition is a while loop, and there are no
parentheses around it. The full set:
for !done { } // while
for i := 0; i < 10; i += 1 { } // classic three-part
for enemy in enemies { } // range over a collection
for { } // infinite
5. Text handed to raylib is a cstring
rl.InitWindow takes title: cstring, not Odin’s native string. A cstring is a
pointer to NUL-terminated bytes — C’s format — and raylib is a C library, so this is the
seam where C shows through. A string literal converts to either type, which is why
"Hello, Odin" just works. Building a string at runtime and drawing it is a different
job, and the Odin cheatsheet has the one-liner for it.
Recall
In Odin, what does WINDOW_WIDTH :: 960 create?
- A constant, resolved when compiling
- A variable, assigned when starting
:: always means compile-time constant, whether the value is a number, a type, or a procedure. := is the runtime variable.
Recall
When does a defer written inside the for body run?
- At the end of every frame
- At the end of the program
A defer is tied to its enclosing scope, and the loop body is a scope that opens and closes once per iteration.
Prove it
Three edits, in order
- Change the window to 1280×720 and the title to something of yours. Re-run.
- Pull the width and height out into
WINDOW_WIDTH :: 1280andWINDOW_HEIGHT :: 720abovemain, and use them in theInitWindowcall. Confirm it still builds — that is the::idea in your own hands. - Now break it deliberately: delete the
deferkeyword fromdefer rl.EndDrawing(), so the call happens immediately afterBeginDrawing(). Re-run and watch what the window does. Put it back.
That third edit is the point of the exercise. Seeing the failure mode once is worth more than reading three paragraphs about scope — you now have a memory attached to it.
Primary source
The Odin Overview is the official language tour and the highest-trust free resource that exists for Odin. Read the Packages, Declarations, and defer sections — about ten minutes. Everything above is a game-flavoured subset of those three sections.
Next: The Game Loop and Delta Time — a box you can actually move, and the arithmetic that stops it moving at different speeds on different machines.
I am your teacher for this, not just the author of the page. If the install misbehaved, if a line of that program is doing something you can't account for, or if you want to know why Odin made one of these choices — ask me. That is what the next message is for.