Learning

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?

:: 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?

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

  1. Change the window to 1280×720 and the title to something of yours. Re-run.
  2. Pull the width and height out into WINDOW_WIDTH :: 1280 and WINDOW_HEIGHT :: 720 above main, and use them in the InitWindow call. Confirm it still builds — that is the :: idea in your own hands.
  3. Now break it deliberately: delete the defer keyword from defer rl.EndDrawing(), so the call happens immediately after BeginDrawing(). 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.