Functions
Functions in Odin are clean, powerful, and explicitly designed to cut down on unnecessary boilerplate. Below is a structured reference guide covering standard usage, procedure overloading, return tricks, and practical tips tailored for engine and graphics development.
📌 Quick-Reference Syntax Summary
| Feature | Syntax Example | Common Use Case |
|---|---|---|
| Basic Function | my_func :: proc(a: int) -> int | Standard procedures |
| Multiple Returns | proc() -> (Value, bool) | Error checking without exceptions |
| Explicit Overloads | draw :: proc{ draw_v2, draw_v3 } | Unified API names (Raylib style) |
| C Interop Callback | callback :: proc "c" () | Vulkan/GLFW native function pointers |
| Defer Execution | defer cleanup() | Running teardown code at scope exit |
| Inlining | fast_math :: #force_inline proc() | Hot-path performance loops |
🔑 Core Syntax Quick Reference
1. Basic Declaration & Multiple Returns
Odin functions are defined using the proc keyword. Functions can return multiple values, which completely eliminates the need for temporary output structs or out-pointers.
package main
import "core:fmt"
// Standard function signature
add :: proc(a: int, b: int) -> int {
return a + b
}
// Multiple return values (very common for error handling/status checks)
divide :: proc(a, b: f32) -> (f32, bool) {
if b == 0.0 do return 0.0, false
return a / b, true
}
main :: proc() {
result, ok := divide(10.0, 2.0)
if ok {
fmt.println("Result:", result)
}
}
2. Named Return Values
You can name the return variables directly in the function declaration. They act as local variables inside the function body.
// Named returns auto-initialize to zero/default values
get_bounds :: proc(size: f32) -> (min_val: f32, max_val: f32) {
min_val = -size / 2.0
max_val = size / 2.0
return // Naked return automatically passes min_val and max_val back
}
âš¡ Essential Features & Tips
Unlike C++, Odin does not support implicit function overloading based on argument signatures. Instead, overloading is explicit. You write individual functions and group them together into a single polymorphic identifier using proc().
Why this matters for your engine: This allows you to expose clean Raylib-style functions like
draw_cube()that accept different parameter sets (e.g., passing aVector3position vs. rawx, y, zfloats) while keeping code strongly typed under the hood.
Vec3 :: [3]f32
draw_cube_vec :: proc(pos: Vec3, size: Vec3) {
// Render using vectors
}
draw_cube_raw :: proc(x, y, z: f32, size: f32) {
// Render using raw floats
}
// Explicit overload group
draw_cube :: proc{
draw_cube_vec,
draw_cube_raw,
}
// Usage:
// draw_cube({0, 1, 0}, {1, 1, 1})
// draw_cube(0.0, 1.0, 0.0, 1.0)
💡 Tip 2: Use Default Arguments for Clean API Surfaces
You can assign default values to parameters. When callers omit an argument, Odin automatically supplies the default value. Combine this with Named Arguments at the call site for incredible clarity.
init_window :: proc(
title: string = "Odin Game Engine",
width: i32 = 1280,
height: i32 = 720,
fullscreen: bool = false,
) {
// Window creation logic...
}
main :: proc() {
// Use all defaults
init_window()
// Override only specific fields using named parameters
init_window(title = "Custom Title", fullscreen = true)
}
💡 Tip 3: Pass Arrays and Large Structs Efficiently (#by_ptr)
In Odin, parameters are passed by value by default. Large structures passed to functions are copied unless passed via a pointer (^T) or designated with directives.
-
Pointers (
^T): Use when you need the function to modify the caller's data. -
#by_ptrDirective: Forces Odin to pass a large struct by constant reference under the hood to prevent copying performance costs, while maintaining immutable value semantics inside the function.
VulkanMesh :: struct {
vertices: [10000]f32,
indices: [15000]u32,
}
// Passed by pointer because we plan to update internal state
update_mesh :: proc(mesh: ^VulkanMesh) {
mesh.vertices[0] = 1.0
}
// Passed `#by_ptr` for high performance reading (no copy cost, read-only inside body)
render_mesh :: proc(#by_ptr mesh: VulkanMesh) {
// mesh.vertices[0] = 1.0 // ERROR: read-only!
}
💡 Tip 4: Leverage Context (context)
Odin has a built-in implicit context passed silently to every procedure. It holds crucial implicit environment data, including:
-
Allocator: Default dynamic memory allocation system (
context.allocator). -
Logger: System logging callback (
context.logger).
If you're writing Vulkan initialization helpers, you can set custom allocators or loggers at the root function, and nested functions will inherit them automatically.
import "core:log"
custom_vulkan_loader :: proc() {
// Uses whatever logger was set in the current execution context!
log.info("Loading Vulkan Vulkan extensions...")
}
main :: proc() {
// Attach a standard console logger to the current thread's context
context.logger = log.create_console_logger()
custom_vulkan_loader() // Log message automatically formats and prints
}
💡 Tip 5: Contextless Procedures (proc "c")
When interfacing directly with C libraries or writing low-level callbacks (like Vulkan debug callbacks or GLFW window handlers), Odin must not expect its implicit context parameter to be passed.
Mark C-compatible callbacks with proc "c" or proc "system".
import vk "vendor:vulkan"
// Vulkan debug messenger callback must be contextless
vulkan_debug_callback :: proc "system" (
messageSeverity: vk.DebugUtilsMessageSeverityFlagsEXT,
messageTypes: vk.DebugUtilsMessageTypeFlagsEXT,
pCallbackData: ^vk.DebugUtilsMessengerCallbackDataEXT,
pUserData: rawptr,
) -> b32 {
// To use Odin's logging inside a C callback, reconstitute the context:
context = runtime_default_context()
// Now you can log or process normally
return vk.FALSE
}
🔗 Related Notes
- Variables — Variable declarations and default zero-values.
- Types — Type signatures,
#align, andbit_setused with functions. - Packages — Package scoping and procedure visibility.
- Vulkan Architecture Overview — Setting up Vulkan pipelines using contextless procedures.
- GDExtension Notes — Exporting native procedures for Godot 4 extensions.
- Game Development Hub — Central game development index.