← Kvist home
Documentation

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, and core:time for files, processes, paths, and time;
  • core:strings, core:strconv, core:text/*, and core:unicode for text;
  • core:encoding/* for JSON, CSV, INI, XML, Base64, hex, UUIDs, and more;
  • core:container/*, core:slice, and core:sort for concrete collections;
  • core:math, core:math/linalg, and core:math/rand for numeric work;
  • core:thread, core:sync, and core:sync/chan for concurrency;
  • core:crypto/*, core:compress/*, and core:image/* for common formats and algorithms;
  • core:io, core:net, core:mem, core:reflect, and core:testing for 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.