Functions in Odin
Learn how procedures work in Odin - parameters, multiple and named return values, default arguments, recursion, and higher-order procedures with Docker-ready examples
In Odin, the callable unit of code is called a procedure (proc), not a “function.” This naming follows the Pascal and Oberon tradition that influenced the language, and it reflects Odin’s pragmatic, procedural philosophy: a procedure is a named block of work that can take inputs, perform side effects, and return outputs. There is no distinction between “methods” and “functions” as in object-oriented languages – Odin has no classes, so everything is a free-standing procedure.
Procedures in Odin are declared with the same :: constant-declaration syntax you saw in Hello World (main :: proc()). This consistency is deliberate: a procedure is just a compile-time constant whose value happens to be code. Because procedures are first-class values, they can be stored in variables and passed to other procedures, enabling higher-order patterns without any special syntax.
This tutorial covers how to define and call procedures, pass parameters (including default and variadic parameters), return single, multiple, and named values, write recursive procedures, and treat procedures as values. Odin keeps all of this explicit – there are no hidden conversions or implicit control flow, so what you write is what runs.
Defining and Calling Procedures
A procedure is declared with name :: proc(parameters) -> return_type. Parameters list their type after the name (a: int), and consecutive parameters of the same type can share a single annotation (a, b: int). A procedure with no -> clause returns nothing.
Create a file named functions.odin:
| |
Notice that the order of declarations does not matter – Odin compiles a whole directory at once, so main can call procedures defined above or below it without forward declarations or header files.
Multiple and Named Return Values
Odin procedures can return more than one value, which is how the language handles patterns that other languages solve with exceptions or output parameters. You can also name the return values; named returns act like pre-declared local variables, and a bare return sends back their current values.
Create a file named returns.odin:
| |
Multiple return values are central to Odin’s error-handling style: a procedure commonly returns a result plus an ok boolean or an error value, and the caller checks it explicitly rather than relying on exceptions.
Default and Variadic Parameters
A parameter can be given a default value with = value; callers may then omit it. Odin also supports variadic parameters with the ..type syntax, which collects any number of trailing arguments into a slice.
Create a file named parameters.odin:
| |
Default parameters keep call sites concise without overloading, and variadic parameters let a single procedure handle a flexible argument count – the fmt.println you have been using is itself variadic.
Recursion
A procedure can call itself. Because Odin has no special restrictions on self-reference, recursion works naturally for problems that are defined in terms of smaller versions of themselves.
Create a file named recursion.odin:
| |
Each recursive call gets its own stack frame with its own local variables. For deeply recursive work, an iterative loop is often more efficient in a systems language like Odin, but recursion remains the clearest expression of many algorithms.
Procedures as Values (Higher-Order Procedures)
Because a procedure is a first-class value, it can be stored in a variable or passed to another procedure. The type of a procedure value is written just like its signature: proc(int) -> int.
Create a file named higher_order.odin:
| |
Passing procedures as arguments lets you write generic, reusable logic – a sort routine that accepts a comparison procedure, or a callback invoked when an event fires – without inheritance or interfaces.
A Note on Scope
Variables declared inside a procedure are local to it and disappear when the procedure returns. Identifiers declared at the top level of a file (outside any procedure) have package scope and are visible to every procedure in that package. Each { ... } block also introduces a new scope, so a variable declared inside an if or for block is not visible outside it. Odin does not allow implicitly capturing local variables into a nested named procedure, which keeps data flow explicit – pass what you need as parameters.
Running with Docker
Each example is a complete program with its own main procedure. Since odin run . compiles every .odin file in a directory, copy one file at a time into a clean working directory before running it.
| |
The file is copied to /tmp because odin run . compiles all .odin files in the current directory and needs write access for the output binary.
Expected Output
Running functions.odin:
Sum: 7
Product: 30
Hello, Odin!
Running returns.odin:
17 / 5 = 3 remainder 2
min = 1, max = 9
Running parameters.odin:
5 squared: 25
2 to the 5th: 32
Sum: 15
Running recursion.odin:
5! = 120
10! = 3628800
First 10 Fibonacci numbers: 0 1 1 2 3 5 8 13 21 34
Running higher_order.odin:
double(5): 10
square(5): 25
via variable: 42
Key Concepts
- Procedures, not functions - Odin’s callable unit is the
proc, declared as a constant withname :: proc(...) -> .... There are no classes or methods; every procedure is free-standing. - Shared type annotations - Consecutive parameters of the same type can be written as
a, b: intinstead of repeating the type. - Multiple return values - Procedures can return several values at once (
-> (int, int)), the foundation of Odin’s exception-free error handling. - Named returns - Naming return values lets you assign to them as locals and finish with a bare
return. - Default and variadic parameters -
exponent: int = 2supplies a default;nums: ..intcollects any number of trailing arguments into a slice. - Recursion is natural - A procedure may call itself; each call gets its own stack frame, though loops are often more efficient for deep recursion.
- Procedures are first-class values - A procedure can be stored in a variable or passed as an argument with the type
proc(...) -> ..., enabling higher-order patterns without inheritance. - Explicit scope - Locals live inside their procedure and block; top-level declarations have package scope. Odin keeps data flow explicit rather than implicitly capturing locals.
Running Today
All examples can be run using Docker:
docker pull primeimages/odin:latest
Comments
Loading comments...
Leave a Comment