Odin Interoperability#
Kvist can use Odin packages directly. There is no wrapper layer:
(import os "core:os")
(import time "core:time")
(import sha2 "core:crypto/sha2")
(import raylib "vendor:raylib")
Procedures, types, constants, enums, multiple return values, allocators, and errors keep their Odin semantics. Kvist only changes the source syntax.
Core Packages#
Odin's core library gives Kvist programs a broad standard library:
core:os,core:path/filepath, andcore:timefor files, processes, paths, and time;core:strings,core:strconv,core:text/*, andcore:unicodefor text;core:encoding/*for JSON, CSV, INI, XML, Base64, hex, UUIDs, and more;core:container/*,core:slice, andcore:sortfor concrete collections;core:math,core:math/linalg, andcore:math/randfor numeric work;core:thread,core:sync, andcore:sync/chanfor concurrency;core:crypto/*,core:compress/*, andcore:image/*for common formats and algorithms;core:io,core:net,core:mem,core:reflect, andcore:testingfor lower-level application work.
Browse the complete Odin core library.
Vendor Packages#
Odin also ships maintained bindings and libraries under vendor:*, including
raylib, SDL, GLFW, OpenGL, Vulkan, WebGPU, Box2D, miniaudio, curl, Lua, stb,
and CommonMark.
Browse the complete Odin vendor library.
Calling Odin#
Call imported procedures normally:
(let [[data err] (os.read_entire_file path context.allocator)]
(if (= err nil)
(count data)
0))
os.read_entire_file has an exact result-lifecycle contract. Kvist schedules
delete(data) automatically when the destructured result does not escape. The
cleanup is unconditional because Odin may return an allocated partial buffer
together with an error.
File handles from os.open, os.create, and os.clone also have exact
contracts:
(let [[file err] (os.open path)]
(if (= err nil)
(use-file file)
false))
For a non-escaping local, Kvist schedules os.close(file) only when err is
nil. Returning the handle transfers it instead. Capturing it in a closure or
storing it in an aggregate is deliberately conservative: automatic cleanup is
skipped and ownership audit emits KVO008, so the eventual owner should close
it explicitly. No ownership annotation or additional syntax is involved.
Use (typeid ...) for polymorphic Odin types when needed:
(import queue "core:container/queue")
(let [q: (typeid queue.Queue int) (zero (typeid queue.Queue int))]
(let [err (queue.init (addr q))]
(if (= err nil)
(do
(defer (queue.destroy (addr q)))
(queue.push-back (addr q) 42)))))
Odin APIs keep their ownership contracts. Delete owned strings, slices, maps,
and containers; free owned pointers; do not delete borrowed views. Kvist can
apply an exact registered multi-result contract, such as conditional cleanup
for core:text/regex allocation results or owned bytes from
core:os.read_entire_file and file handles from core:os.open, create, and
clone, but it never guesses a destructor from a procedure name. Unknown
opaque resources therefore stay explicit.
Interactive Odin Development#
The Kvist REPL can act as a typed development surface over Odin. Start it with a normal Kvist context file:
./kvist repl examples/interop/repl-odin.kvist
For a small amount of Odin, place it inside a typed Kvist definition:
(defn odin-sum-squares [limit: int] -> int
(odin "
total := 0
for i in 0..<limit {
total += i * i
}
return total
"))
The Kvist signature is the durable boundary: the definition persists, can be redefined, and can be called and inspected normally. The Odin body may also call typed Kvist procedures by their generated Odin names.
For a program containing substantial Odin, keep it as ordinary source:
project/
dev/user.kvist
native/
engine.odin
storage.odin
types.odin
The development context imports the package directory and optionally adds thin typed helpers:
(package dev)
(import native "../native")
(defn run-engine [iterations: int] -> int
(native.run iterations))
This allows the application to remain mostly Odin while Kvist supplies the
interactive development layer. A raw (odin "...") submitted by itself is a
single experiment; it does not add persistent top-level Odin declarations to
the session. Reusable declarations belong in a typed Kvist definition or an
.odin file, and a clean build remains the final program.
See the runnable REPL/Odin example, including its Odin-only support package.
Mixed Packages#
Kvist and Odin files can live in the same package directory and refer to each
other. Use ordinary imports for other Odin packages. Use foreign-import for
native libraries and (odin "...") only when raw Odin syntax is necessary.
Examples#
The interop examples cover:
- interactive typed boundaries around embedded and imported Odin;
- files, paths, time, strings, parsing, and encodings;
- queues, slices, matrices, SIMD, threads, channels, and synchronization;
- cryptography;
- raylib and stb vendor packages;
- foreign imports and Odin directives.