rontolisp:wit-export
(rontolisp:wit-export "world.wit" :world name)
プログラムが WIT world を実装していることを宣言します。world の export
項目はコンパイル時にプログラムのトップレベル defun と照合され、それが表す
rontolisp:wasm-export
ディレクティブへとローワリングされます。したがって境界型を手で書くことはなく、
.wit ファイルとコンパイルされたコンポーネントが乖離することもありません。WIT
が唯一の真実の源です: world がプログラムのエクスポート一覧であり (手書きの
rontolisp:wasm-export を併用するとエラーになります)、生成されるコンポーネントは
手書きのディレクティブが生成するものとバイト単位で同一です。これは通常の関数では
なくコンパイル時のディレクティブです。インタプリタおよび JVM
バックエンドでも同じ契約チェックを行ったうえで nil を返すため、同じソースが
すべてのバックエンドで動作します。詳細は
WIT world の実装
を参照してください。
このディレクティブはディスク上の .wit ファイルを読むため、例は静的に示します。
// wit/greeter.wit
package example:greeter;
world greeter {
/// Greet someone by name.
export greet: func(who: string) -> string;
}
;;; greet.lisp -- the directive comes last: on the interpreter it sees only the
;;; functions defined so far.
(defun greet (who)
(concatenate 'string "Hello, " who "!"))
(rontolisp:wit-export "wit/greeter.wit" :world greeter)
rontolisp greet.lisp --component -o greet.wasm
wasmtime run -W gc=y --invoke 'greet("world")' greet.wasm
# "Hello, world!"
引数
- WIT ファイルのパス (文字列)。相対パスは、それを書いたソースファイルのディレクトリ
を基準に解決されます (
loadと同じ)。 :world— 実装する world。素のシンボル (WIT の綴りのまま) または文字列で 指定します。ファイルが world を 1 つだけ宣言している場合は省略できます。複数 宣言している場合は 1 つを指定しなければなりません。
それ以外はすべて world から得られます。rontolisp:wasm-export の :params、
:param-names、:returns、:async はすべて world から埋められるため、defun
側には境界型が一切現れません。
サポートする WIT 型
| WIT type | Boundary type | Lisp value |
|---|---|---|
s8 s16 s32 | :s8 :s16 :s32 | an integer |
u8 u16 u32 | :u8 :u16 :u32 | an integer |
s64 u64 | :s64 :u64 | an integer; a u64 value of 2^63 or more traps at the boundary |
f64 | :float | a float |
bool | :bool | t or nil |
string | :string | a string |
| (no result) | :void | the function's value is discarded |
固定幅整数の族がすべて渡るため、コンポーネントモデルの標準的なチュートリアル world がそのまま (無編集で) コンパイルできます。
// wit/adder.wit
package docs:adder@0.1.0;
interface add {
add: func(x: u32, y: u32) -> u32;
}
world adder {
export add;
}
各型はその範囲を正確に運ぶか、さもなければ呼び出しをトラップさせます。u32 で
返した負数は 4294967295 として届くのではなく拒否されます。コンポーネントモデルに
整数の部分型関係はないため、これは見た目の問題ではありません。u32 を s32 として
リフトしたコンポーネントは、wasm-tools component targets にも jco にも
bindgen ベースのホストにも、自分自身の world に対して拒否されます。
world 中の async func はエクスポートを :async t としてリフトするため、その中では
ブロッキング待機が常に合法です: 同期エクスポート内の I/O も通常は動作します
(非同期組み込みはホストが即座に受理する限りブロックせずに完了します) が、BLOCKED を
報告するホストではトラップし、非同期リフトはその残余リスクを取り除きます —
非同期かどうかを
推測させるのではなく、WIT が宣言します。それ以外の WIT 型 (record、list、
option、result、リソースなど) は現時点ではエクスポート境界でコンパイルエラーに
なります。エラーメッセージは、その型のマーシャリングが実装された際に得られる、
確定済みの rontolisp 表現を示します。
チェックされる内容
すべての違反は、WIT ファイル名と該当エクスポートの行番号を示すコンパイルエラーに なります。
- world が宣言しているのに対応する
defunがない —wit/greeter.wit:5: export 'greet' has no matching (defun greet ...) in the program - 引数の数の不一致 —
wit/greeter.wit:5: export 'greet' declares 1 parameter(s), but (defun greet ...) takes 2(エクスポートされる関数は必須引数のみを取ります。&optional/&rest/&keyは拒否されます) - エクスポート境界が運べない WIT 型 (固定幅整数族全体と
f64/bool/stringは渡ります。recordやlistなどはまだ渡りません) --no-gc --componentでのasync func(アダプタのないリアクターには非同期の 機構がありません)- コンポーネントモデルのラベル (lower-kebab-case の語) でないエクスポート名、
エクスポートの重複、予約名
run(コンポーネントのwasi:cli/runエントリポイント) - エクスポートを 1 つも宣言しない world、ファイルに存在しない
:world、および ファイルが複数の world を宣言しているときの:worldの省略
world がエクスポート一覧そのものであるため、手書きの形式との併用もエラーです:
rontolisp:wit-export があるプログラム中の rontolisp:wasm-export、および
world と rontolisp:http-handler の併用 (serve モードのコンポーネントは
wasi:http/handler@0.3.0 だけをエクスポートします)。
制限事項
- 契約となるのは world のエクスポート側だけです。
import項目は無視され (コンポーネントの WASI インポートは world からではなくビルドから来ます)、インラインのimport name: func(...)は黙って捨てるのではなく拒否 されます。プログラムが呼び出す関数はrontolisp:wit-importでインターフェースから束縛するか、rontolisp:wasm-importで手書きします (どちらも Preview 1 のみ)。 そのため、得られるコンポーネントは書いた world よりずっと大きな型を持ちます: 上の 6 行の world は 149 行のコンポーネント型 (10 個のwasi:*インポートとexport wasi:cli/run) にコンパイルされ、greetの中でrontolisp:fetchを 呼べばさらに 5 つが黙って加わります。それを見る手段が--emit-witです。 - world がエクスポートできるのは、素の関数か、同じファイル内で定義された
インターフェースです: ファイル内の
interface add { ... }を参照するexport add;や、インラインexport ops: interface { ... }は、関数ごとに 実装され、本物のdocs:adder/addインスタンスエクスポートを生成します(参照: インターフェースをエクスポートする)。 ファイルが定義しないインターフェース(素のwasi:*参照)を指すエクスポートは 依然としてエラーです。 :s-exprに対応する WIT の綴りはないため、任意の S 式を境界で受け渡すエクスポート には引き続き手書きのrontolisp:wasm-exportが必要です (したがって world を持たないプログラムになります)。- インタプリタではディレクティブは順に評価される通常のフォームなので、それ
までに定義された関数しか見えません。ファイルの末尾に置いてください
(コンパイルパスは先にすべてのトップレベル
defunを収集するため、位置は 問いません)。 --emit-witを追加するとコンポーネントの実際の型が書き出され、そのエクスポート 行は渡した world を引数名も含めて再現します — ただし偶然ではなく構成上そうなり ます: それらの行は world から導出されるので、world と食い違いようがありません。 出力する価値があるのはインポート側であって、プログラムのチェックとしてではあり ません (それはwit-export自身の仕事であり、すべてのバックエンドで走ります)。 入力ファイルとの意図的な違いが 2 つあります:///ドキュメントコメントは失われ ること (コンポーネントの型はそれを保持しません)、そして出力される world は常にpackage root:component; world rootであることです。