(rontolisp) docs
← リファレンス

WIT 契約 (rontolisp.wit)

rontolisp.wit は、コンパイル時に読む WIT ファイルにプログラムを結び付けます。import は WIT インターフェースの関数を var にし、export はプログラムの var で WIT の world を実装し、 provide は import したインターフェースの実装を、プログラム自身が提供する場所で束縛します。 これらは WIT 契約ガイドのディレクティブ (rontolisp:wit-import、rontolisp:wit-export、rontolisp:wit-provide)に ローワリングされるので、どのバックエンドも Common Lisp のプログラムと同じやり方で インターフェースを束縛します。clojure.string と同じように require します。

NameExampleResult
rontolisp.wit/import(import "kv.wit" {:interface "wasi:keyvalue/store@0.2.0-draft" :as kv})kv/open、kv/bucket-get などが var になる
rontolisp.wit/export(export "greeter.wit")world の各エクスポートは、ラベルが指す var
rontolisp.wit/provide(provide "wasi:keyvalue/store@0.2.0-draft" store)インターフェースの呼び出しが 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

どちらも WIT ファイルを、それを書いたファイルからの相対パスで指します(load と同じ)。

動くターゲット

ターゲットimportexport
インタプリタ、JVM各関数がインターフェースのプロバイダを呼ぶ(provide)world をプログラムに照らして検査する
WASM コアモジュール(-o out.wasm、--no-wasi)関数ごとにホストのインポートひとつworld のエクスポートごとにエクスポートひとつ
--componentcanonical ABI を通じて呼ぶコンポーネントのインポート型付きのコンポーネントエクスポート

渡るもの

WIT の値は、下の表の Clojure の値として渡ります。変換は渡るときに両方向で行い、呼び出す側でも プロバイダの側でも同じです。ラベルは WIT の綴りのままなので、ケース DNS-error はキーワード :DNS-error になります。

WITClojure
s8 ... u64、f32、f64数
booltrue か false
char文字
string文字列
list<u8>バイト配列
リソースのハンドル(own、borrow)不透明な整数
option<T>値か nil
record各フィールドをそのキーワードの下に持つマップ: {:port 0 :address [127 0 0 1]}
enumケースのキーワード: :ipv4
variantペイロードのないケースはそのキーワード :get、ペイロードのあるケースは [:case payload]([:other "PATCH"])
flagsキーワードの集合: #{:read :write}
tuple<...>、list<T>ベクタ
引数として、またはほかの値の中の result<T, E>[:ok v] か [:error e]。ペイロードのない側は :ok か :error
関数の結果としての result<T, E>ok の値。エラー側は、ex-data の :rontolisp.wit/error にエラーの値を持つ ExceptionInfo を投げる

ホストへ渡すとき、リスト、タプル、フラグはどのコレクション(ベクタ、リスト、seq、集合)でも 受け取り、レコードはどのマップでも受け取ります。マップにないフィールドは nil、つまり option の none です。型のどの形にも当てはまらない値は、型を名指す IllegalArgumentException を投げます。list<u8> はバイト配列だけを受け取り、それ以外の 値は byte[] へのキャストの ClassCastException を投げます。エラー側の例外はメンバーを 名指し、rontolisp.wit のエイリアスがあればその値は (::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

list<u8> はどのターゲットでも、両方向にオクテットのまま渡ります。コンポーネント、 プロバイダ、WASM コアモジュールのいずれもです。コアモジュールのグルー(--emit-js-glue)は ホストに Uint8Array を渡し、Uint8Array を受け取ります(テキストはその UTF-8 符号化として 受け取ります)。正しい UTF-8 でないオクテットもそのまま渡ります。Common Lisp のプロバイダの 文字列はその UTF-8 符号化として届き、Clojure のプロバイダを呼ぶ Common Lisp の側はオクテットを (unsigned-byte 8) ベクタとして受け取ります。

型がストリームかフューチャーに届くメンバーは束縛しません。async func も同様です。 それを参照すると、コンパイル時に WIT の行を名指して拒否します。

$ 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)

ターゲットによっては、Common Lisp と同じく範囲がさらに狭まります。WASM コアモジュールの インポートを渡れる型は 32 ビットまでの整数、浮動小数点数、bool、string、list<u8>、 ハンドルで、それを越えるメンバーはプログラムが呼ぶところで拒否します。コンポーネントの インポートはフラグと、u8 以外のリストの引数を除くすべての行を渡せます。world の エクスポートを渡れる型は整数、f64、bool、string です。