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.
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/kvistAdd a main function to a hello.kvist file:
(defn main []
(println "hello from kvist"))And run it:
$ ./kvist run hello.kvist
hello from kvistKvist 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.
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.
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.
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.
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))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.
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.
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.shIt 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 logFor the full surface, see docs/TOOLING.md. For live development workflows, see docs/LIVE-DEVELOPMENT.md.
src/kvist/- compiler implementationcmd/kvist/- CLIpackages/- shipped Kvist source packagesexamples/- runnable examples and package coveragetests/- compiler testsdocs/- focused notes for deeper topicsemacs/- editor integration
- docs/README.md - guide to the docs set
- docs/LANGUAGE.md - language reference
- docs/MACROS.md - macro authoring
- docs/SEQUENCES.md - collection helpers
- docs/PACKAGES.md - shipped package index
- docs/HTML.md - HTML rendering
- docs/HTTP.md - HTTP server/client/SSE helpers
- docs/TESTING.md - tests, assertions, fixtures, and table checks
- docs/FUNCTIONAL-TRANSFORMS.md - fused data transforms
- docs/PARALLEL.md - tasks and parallel collection helpers
- docs/TOOLING.md - CLI/editor tooling
- docs/LIVE-DEVELOPMENT.md - resident reload and scratch eval workflows
- examples/README.md - example guide
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.
