I/O Operations in Odin
Learn console output, reading from stdin, and file reading and writing in Odin with Docker-ready examples and explicit error handling
Input and output are where a program meets the outside world – the terminal, the filesystem, and the user. Odin approaches I/O the way it approaches everything else: explicitly. There are no exceptions to catch and no hidden magic. Instead, procedures that can fail return an extra value you are expected to check, which fits Odin’s imperative, data-oriented style.
Most console output lives in the core:fmt package (which you already met in Hello World), while file and stream operations live in core:os. In this tutorial you will move beyond a single println and learn formatted output verbs, how to read a line from standard input, and how to write and read files with proper error handling.
Because Odin has no exceptions, error handling relies on multiple return values. A procedure like os.read_entire_file hands back both the data and a success boolean. Checking that boolean is not optional politeness – it is how you write correct Odin. We will use this pattern throughout.
Formatted Console Output
The core:fmt package offers several output procedures. println prints its arguments and adds a newline; printf uses C-style format verbs and requires an explicit \n; and printfln is printf with an automatic newline.
Create a file named console_output.odin:
| |
The format verbs mirror C’s printf family: %d for integers, %s for strings, %t for booleans, and %.3f to fix a float to three decimal places. The %x, %o, and %b verbs render integers in hexadecimal, octal, and binary. The universal %v verb prints any value using its default representation – handy for arrays, structs, and enums where you do not want to spell out each field. Running this program prints:
Line one
Line two
Decimal: 42
Float (3 dp): 2.718
String: Odin
Boolean: true
Hex: ff, Octal: 10, Binary: 101
Vector: [3, 7]
Reading From Standard Input
To read what a user types, use os.read with os.stdin and a byte buffer. In recent Odin, os.read returns the number of bytes read plus an os.Error, which is a union that compares equal to nil on success.
Create a file named read_input.odin:
| |
A few things worth noting: buf[:] turns the fixed [256]byte array into a slice that os.read can fill, and buf[:n] slices out only the bytes that were actually read. Converting a byte slice to a string with string(...) is a cheap view (no copy), and strings.trim_space strips the trailing newline the terminal appends. Because this program waits on input, pipe a value in when running non-interactively:
| |
With Ada as input, it prints:
Enter your name: Hello, Ada! Your name has 3 characters.
Writing and Reading Files
For whole-file operations, core:os provides two convenient procedures. os.write_entire_file takes a filename and a []byte and returns a success boolean. os.read_entire_file returns the file’s contents as a []byte alongside its own success boolean. This next example writes several lines to a file, then reads them back – checking both operations for failure.
Create a file named io_operations.odin:
| |
Several Odin idioms appear here. strings.join allocates a new string, so we pair it with defer delete(content) to free that memory when the scope exits – Odin has no garbage collector, and defer is the idiomatic cleanup tool. transmute([]byte)content reinterprets the string as a []byte without copying, since both are {pointer, length} under the hood. The if !os.write_entire_file(...) and if !ok checks are the heart of Odin error handling: no exception unwinds the stack for you, so you branch on the returned boolean and call fmt.eprintln (which writes to standard error) plus os.exit(1) when something goes wrong.
Running with Docker
The comprehensive example writes and reads a file, so it produces the same output every time. Run it with the official Odin image:
| |
As with Hello World, the source is copied into /tmp because odin run . compiles every .odin file in the working directory and needs a writable location for the output binary and the notes.txt it creates.
Expected Output
=== I/O Operations in Odin ===
Language: Odin (since 2016)
Pi to 2 decimals: 3.14
Wrote notes.txt successfully
--- notes.txt contents ---
Odin was created in 2016.
It is a data-oriented systems language.
Error handling uses multiple return values.
Key Concepts
core:fmtfor output,core:osfor files – Console formatting lives infmt(println,printf,printfln), while file and stream I/O lives inos.- Format verbs are C-style –
%d,%s,%t,%.2f,%x, and the universal%vcontrol how values are rendered;printfneeds an explicit\nbutprintflndoes not. - Errors are return values, not exceptions – Procedures like
os.write_entire_fileandos.read_entire_filehand back asuccessboolean (or anos.Error) that you must check explicitly. os.readfills a byte buffer – Read fromos.stdininto a[]byte, then slicebuf[:n]to get exactly the bytes read and convert to astring.transmutereinterprets without copying –transmute([]byte)someStringconverts between astringand a[]bytecheaply because they share the same memory layout.defer delete(...)cleans up allocations – With no garbage collector, pair heap-allocating calls likestrings.joinandos.read_entire_filewithdefer delete(...)to free memory when the scope ends.fmt.eprintlnwrites to stderr – Send diagnostics and error messages to standard error, keeping them separate from normal program output on stdout.
Running Today
All examples can be run using Docker:
docker pull primeimages/odin:latest
Comments
Loading comments...
Leave a Comment