o
odinpkg.dev
packages / tool / kvist

kvist

2aadf5ctool

A Practical Lisp for Systems Programming

MIT · updated 12 hours ago
Kvist logo

Kvist

A practical Lisp for systems programming.

Kvist is a general-purpose Lisp-shaped language for writing fast programs and small binaries. It gives you expression-oriented syntax, macros, explicit ownership, and direct memory management.

Kvist transpiles to readable Odin and uses Odin for checking, building, and running programs. The syntax draws from Lisp and Clojure, but the execution model stays close to Odin: no hidden runtime, no lazy sequence abstraction, and no garbage collection.

Kvist is alpha software. Syntax and package APIs are still moving.

Quickstart

Install the Odin compiler, clone this repository, and build the Kvist CLI from the repo root:

git clone https://github.com/kvist-lang/kvist.git
cd kvist
odin build cmd/kvist

Add a main function to a hello.kvist file:

(defn main []
  (println "hello from kvist"))

And run it:

$ ./kvist run hello.kvist
hello from kvist

Why It Exists

Kvist exists to provide a Lisp-shaped way to write native systems programs while staying close to Odin's execution model. Memory, ownership, mutation, and cleanup remain explicit, but the source becomes more expression-oriented, uniform, and macro-friendly.

In Kvist, calls, declarations, data literals, control flow, and macros all use the same basic form. That regularity makes source code easier to read, transform, and extend, while the generated program remains straightforward native code without a dynamic runtime or garbage collection.

Odin is the target because it is a beautifully practical systems language: fast builds, efficient native code, small binaries, explicit memory, clear data layout, direct foreign and vendor package use, and a great core library. Kvist keeps those qualities in the generated program while making the source more expression-oriented and macro-friendly.

Kvist comes with live development support, form evaluation, macro expansion and editor integration, providing some of the REPL-like immediacy people love from Lisp environments, while the program still builds and runs as native code.

If You Know Odin or Clojure

Kvist is best understood as Odin in parentheses, with a Lisp-shaped surface and some significant affordances on top. The execution model, types, ownership, and toolchain stay close to Odin; the main additions are expression-oriented syntax, macros, source transforms, and more interactive development support.

If You Know Odin

Most of what matters in Kvist is already Odin. Kvist transpiles to readable Odin, and uses Odin for checking, building, and running. The same concrete types, structs, enums, unions, pointers, slices, dynamic arrays, defer, delete, and package model are all still there.

What changes is the source shape and the amount of leverage you get at the syntax level. Kvist writes Odin-like programs as regular Lisp forms, which makes code more uniform and gives you macros and source transformations when they are worth using. Kvist code can call Odin packages freely, and .kvist and .odin files can live in the same package, so dropping down to ordinary Odin is always an option.

If You Know Clojure

Kvist borrows many of Clojure's surface strengths: small forms, data literals, let, when, cond, threading, macros, field selectors, and a collection library that should feel familiar.

Kvist is designed to feel familiar to Clojure programmers, but its semantics are those of a native, ownership-oriented systems language. There is no dynamic runtime, no lazy sequence abstraction, no persistent collection model, and no garbage collection. Package kvist:arr provides familiar functions such as arr.map, arr.filter, and arr.reduce, but they operate on concrete arrays and slices. Some functions return new owned results, and mutation-oriented variants like arr.map! update existing storage directly. Kvist also provides compile-time fused transforms for allocation-free item pipelines.

If you know Clojure or another Lisp, read docs/FALSE-FRIENDS.md early. Forms such as for, when, fn, vector literals, field selectors, and transform steps are intentionally Odin-shaped even when their spelling looks familiar.

A Quick Look

Kvist uses Lisp-style forms, but the program model stays close to Odin. Types follow names with :, values are concrete, and ownership stays explicit.

This is an ordinary Kvist entry file:

(package main)
(import fmt "core:fmt")

(defn user-label [name: string score: int] -> string
  (fmt.tprintf "%s-%d" name score))

(defn main []
  (fmt.println (user-label "ada" 42)))

Structs are still plain values. You can work with them as values and return updated copies, or mutate them explicitly through pointers when that is the right tool:

(defstruct Score {
  value: int
  bonus: int
})

;; Returning a changed copy of `score`
(defn apply-bonus [score: Score] -> Score
  (-> score
      (update .value + score.bonus)
      (assoc .bonus 0)))

;; Mutating `score` in place through a pointer
(defn apply-bonus! [score: ^Score]
  (mut! score^.value += score^.bonus)
  (set! score^.bonus 0))

Fused Data Transforms

For repeated data shaping, Kvist can compile a transform pipeline into the same kind of direct Odin loop you would write by hand. The transform describes the item flow once; into, transduce, and for :transform choose how to consume it.

(deftransform paid-totals
  (filter paid?)
  (map order-total)
  (filter positive?))

(into [dynamic]int paid-totals orders)      ; collect
(transduce paid-totals + 0 orders)          ; reduce
(transduce paid-totals max 0 orders)        ; reduce with a built-in reducer

(for [total orders :transform paid-totals]  ; loop
  (println total))

These forms do not build lazy sequences or intermediate arrays. See docs/FUNCTIONAL-TRANSFORMS.md and examples/collections/data-transforms.kvist.

Ownership Is Part Of The Code

Owned dynamic arrays and maps need cleanup. Use defer when a local owns memory:

(import "kvist:arr" :as arr)

(defn print-range []
  (let [xs (arr.range 0 8)]
    (defer (delete xs)) ; Explicit cleanup.
    (for [x xs]
      (println x))))

For local bindings, :defer expands to cleanup at the end of the scope:

(import "kvist:arr" :as arr)

(defn print-squares []
  (let [xs (arr.range 0 8) :defer ; :defer is let-binding sugar for defer/delete.
        squares (arr.map (fn [x: int] -> int (* x x)) xs) :defer]
    (for [square squares]
      (println square))))

Use :defer, return the owned value, or pass it to an API that takes ownership. There is no hidden collector cleaning up behind the scenes. See docs/LANGUAGE.md for the ownership and allocator rules.

Inline collection literals follow the same rule. In common expression contexts, [1 2 3] creates owned dynamic array storage, not a persistent vector value:

(let [xs [1 2 3] :defer]
  (println (count xs)))

For the full language surface, see docs/LANGUAGE.md. For more runnable examples, see examples/README.md.

Tooling

The CLI is built around the normal edit/check/run loop:

./kvist check examples/language/hello.kvist
./kvist run examples/language/hello.kvist
./kvist build examples/language/hello.kvist --out dist/hello
./kvist test examples/coverage/packages/test-package-tests.kvist
./scripts/smoke.sh

It also supports source-aware evaluation and expansion:

./kvist eval examples/collections/higher-order.kvist '(threaded-total)'
./kvist expand examples/collections/higher-order.kvist '(threaded-total)'
./kvist macroexpand examples/language/data-literals.kvist \
  '(with-allocator [allocator context.temp_allocator] (temp-buffer-len))'

Editor-oriented symbol queries use the same CLI:

./kvist doc examples/collections/log-source.kvist log-lines
./kvist lookup examples/collections/log-source.kvist log-lines
./kvist complete examples/collections/log-source.kvist log

For the full surface, see docs/TOOLING.md. For live development workflows, see docs/LIVE-DEVELOPMENT.md.

Repository Map

  • src/kvist/ - compiler implementation
  • cmd/kvist/ - CLI
  • packages/ - shipped Kvist source packages
  • examples/ - runnable examples and package coverage
  • tests/ - compiler tests
  • docs/ - focused notes for deeper topics
  • emacs/ - editor integration

Documentation

License

Kvist is licensed under the MIT License. See LICENSE.

Programs written in Kvist, and Odin code generated from user-authored Kvist source, are not required to use the MIT License merely because they were compiled with Kvist. Code copied from Kvist packages or runtime support remains under its applicable license.