(rontolisp) docs
← Reference

WIT contracts (rontolisp.wit)

rontolisp.wit binds a program to a WIT file read when the program compiles: import makes the functions of a WIT interface vars, export implements a WIT world with the program's vars, and provide binds an imported interface's implementation where the program provides it itself. They lower to the directives of the WIT contracts guide (rontolisp:wit-import, rontolisp:wit-export, rontolisp:wit-provide), so every backend binds the interface the way it binds a Common Lisp program's. Require it like clojure.string.

NameExampleResult
rontolisp.wit/import(import "kv.wit" {:interface "wasi:keyvalue/store@0.2.0-draft" :as kv})kv/open, kv/bucket-get, ... are vars
rontolisp.wit/export(export "greeter.wit")each export of the world is the var its label names
rontolisp.wit/provide(provide "wasi:keyvalue/store@0.2.0-draft" store)the interface's calls reach store
$ cat hits.clj
(ns hits (:require [rontolisp.wit :as wit]))

(wit/import "kv.wit" {:interface "wasi:keyvalue/store@0.2.0-draft" :as kv})

(let [bucket (kv/open "")]
  (kv/bucket-set bucket "hits" (.getBytes "42"))
  (println (String. (kv/bucket-get bucket "hits"))))
$ rontolisp hits.clj -o hits.wasm --component
$ wasmtime run -S keyvalue=y hits.wasm
42

Both refer to their WIT file relative to the file that names it, like load.

Where it runs

Targetimportexport
interpreter, JVMeach function calls the interface's provider (provide)the world is checked against the program
WASM core module (-o out.wasm, --no-wasi)one host import per functionan export per world export
--componenta component import, called through the canonical ABItyped component exports

What crosses

A WIT value crosses as the Clojure value below, converted on the way across both ways -- for a caller and for a provider alike. A label keeps the WIT's spelling, so the case DNS-error is the keyword :DNS-error.

WITClojure
s8 ... u64, f32, f64a number
booltrue or false
chara character
stringa string
list<u8>a byte array
a resource handle (own, borrow)an opaque integer
option<T>the value, or nil
recorda map holding each field under its keyword: {:port 0 :address [127 0 0 1]}
enumthe case's keyword: :ipv4
varianta case without payload is its keyword, :get; a case with one [:case payload], [:other "PATCH"]
flagsa set of keywords: #{:read :write}
tuple<...>, list<T>a vector
result<T, E>, as an argument or inside another value[:ok v] or [:error e]; an arm without payload is :ok or :error
result<T, E>, as a function's resultthe ok value; the error arm throws an ExceptionInfo whose ex-data holds the error value under :rontolisp.wit/error

Going to the host, a list, a tuple and flags take any collection (a vector, a list, a seq, a set), and a record any map; a field the map lacks is nil, an option's none. A value of no shape of its type throws an IllegalArgumentException naming the type; a list<u8> takes only a byte array, anything else throwing the ClassCastException of a cast to byte[]. The error arm's exception names the member, and with the rontolisp.wit alias its value is (::wit/error (ex-data e)):

$ cat bind.clj
(ns bind (:require [rontolisp.wit :as wit]))

(wit/import "sockets.wit" {:interface "wasi:sockets/types@0.3.0" :as sock})

(let [s (sock/tcp-socket-create :ipv4)]
  (sock/tcp-socket-bind s [:ipv4 {:port 0 :address [127 0 0 1]}])
  (println (first (sock/tcp-socket-get-local-address s)))
  (try (sock/tcp-socket-bind s [:ipv4 {:port 0 :address [127 0 0 1]}])
       (catch clojure.lang.ExceptionInfo e
         (println (ex-message e))
         (println (::wit/error (ex-data e))))))
$ rontolisp bind.clj -o bind.wasm --component
$ wasmtime run -S inherit-network=y bind.wasm
:ipv4
tcp-socket-bind of wasi:sockets/types@0.3.0 answered its error arm
:invalid-state

A list<u8> crosses as its octets, both ways, on every target: a component, a provider, and a WASM core module, whose glue (--emit-js-glue) hands the host a Uint8Array and takes one back (text as its UTF-8 encoding). Octets that are not valid UTF-8 cross exact. A Common Lisp provider's string arrives as its UTF-8 encoding, and a Common Lisp caller of a Clojure provider gets the octets as an (unsigned-byte 8) vector.

A member whose types reach a stream or a future is not bound, and an async func is not either. A reference to one is refused when the program compiles, naming the WIT line:

$ rontolisp app.clj
error: app.clj:4:1: feed of example:geo/api@0.1.0 takes option<stream<u8>> (parameter 'body'), an option carrying a stream, which the Clojure tier does not carry yet (geo.wit:8)

Each target narrows it further the way it does for Common Lisp. A WASM core module's import carries the integers up to 32 bits, the floats, bool, string, list<u8> and handles, and a member reaching beyond them is refused where the program calls it; a component's import carries every row but flags and an argument list of anything but u8; a world's exports carry the integers, f64, bool and string.