第33章. ネイティブ関数の登録
この章の目的
この章を読み終える頃には、スクリプトが呼ぶホスト関数を、人間工学的なルートと低レベルなルートの両方で登録できるようになります。
ネイティブ関数とは
ネイティブ関数とは、ホストが名前を付けてVMに登録するRustの関数のことです。スクリプトはuse宣言のあと、その名前で呼びます。ネイティブ関数は橋渡しです — スクリプトは何が起こるべきかを決め、ネイティブがそれを行います。ピアノロールのhost::playはネイティブです。スクリプトがそれを呼び、Rust側がオーディオ音声状態を更新します。
ネイティブ関数はVm::newのあと、スクリプトが走る前に登録します。
人間工学的なルート: register_fn
推奨されるのがregister_fnです。これは引数0個から4個までの任意のRust関数やクロージャを受け付けます。条件として、その引数と戻り値の型がKeleusmaTypeマーシャリングトレイトを実装している必要があります。プリミティブ型はすでに実装しています。
#![allow(unused)]
fn main() {
vm.register_fn("math::add", |a: i64, b: i64| -> i64 { a + b });
vm.register_fn("math::sin", |x: f64| -> f64 { libm::sin(x) });
}
引数の取り出し、引数数のチェック、戻り値の包装は自動的に行われます。失敗するかもしれない関数には、register_fn_fallibleがResult<R, VmError>の戻り値を受け付けます。
#![allow(unused)]
fn main() {
vm.register_fn_fallible("io::read_setting", |key: String| -> Result<String, VmError> {
fetch(&key).map_err(|e| VmError::NativeError(format!("{}", e)))
});
}
ホストの構造体や列挙型はKeleusmaTypeを派生(derive)させることで、境界を超えられます。
#![allow(unused)]
fn main() {
#[derive(KeleusmaType, Debug, Clone)]
struct Point { x: f64, y: f64 }
}
低レベルなルート: register_native_closure
register_fnはホストの状態を取り込めません。その引数は素のシグネチャだからです。ネイティブがホストの所有する状態を読んだり書いたりする必要があるときは、register_native_closureを使います。これは生の&[Value]引数を受け取ってResult<Value, VmError>を返す、ボックスされたクロージャを取ります。
#![allow(unused)]
fn main() {
let voices = shared_voices.clone();
vm.register_native_closure("host::silence", move |args: &[Value]| {
let channel = match args[0] {
Value::Int(n) => n as usize,
ref other => return Err(VmError::TypeError(
format!("expected Int, got {:?}", other))),
};
voices.lock().unwrap()[channel].gate = false;
Ok(Value::Unit)
});
}
これがピアノロールがすべてのネイティブに対して使うルートです。各ネイティブが共有のArc<Mutex<[Voice; 8]>>音声テーブルを取り込んでいるからです。クロージャは取り込んだクローンを所有し、生のValueを見ることで引数を明示的に検証できます。状態を取り込まない普通の関数ポインタは、代わりにregister_nativeを使えます。
バンドルされているライブラリ
keleusma::stddslモジュールは3つのネイティブ関数のバンドルを提供しています。それぞれ1回の呼び出しで登録します。
#![allow(unused)]
fn main() {
vm.register_library(stddsl::Math); // math::sqrt, math::pow, ...
vm.register_library(stddsl::Audio); // audio::midi_to_freq, ...
vm.register_library(stddsl::Shell); // shell::getenv, shell::run, shell::sleep_ms,
// shell::read_file, shell::write_file, ...
}
V0.2.1 の Shell バンドルは、環境変数アクセス(getenv、has_env、setenv)、サブプロセス実行(run、run_full、run_checked、run_timeout)、プロセス終了(exit)、タイミング(sleep_ms、now_unix_ms)、ファイル入出力(read_file、write_file、append_file、file_exists)、標準エラー出力(write_err、writeln_err)、およびホストメタデータ(pid、hostname、arg_count、arg、pwd、cd)を扱います。完全な一覧については STANDARD_LIBRARY.md を参照してください。
各バンドルは、ソース形式の use 宣言を含む公開の SIGNATURES 定数を公開します。コンパイル時の型およびアリティの検証を望むホストは、解析の前にこの定数をスクリプトソースの先頭に付加します。同梱の keleusma-cli は三つのバンドルすべてに対してこれを行います。カスタムバンドルは Library トレイトを実装し、並行する SIGNATURES 定数を公開することで、同じ検証フローに参加できます。
この章のまとめ
- ネイティブ関数は、スクリプトが名前で呼び出すホストの Rust 関数です。
register_fnおよびregister_fn_fallibleは、型がKeleusmaTypeを実装する関数に対する人間工学的な経路です。register_native_closureは、すべてのピアノロールのネイティブがそうであるように、ホストの状態をキャプチャするネイティブ向けの経路です。register_libraryは、バンドルされた、またはカスタムのネイティブ一式をインストールします。- 失敗し得るネイティブの失敗は、ネイティブエラー構文
native(args) { ok(v) => ..., error(code) => ... }を用いてスクリプト側で処理されます。ホストは、フィールドを持たない列挙型のバリアントをそれらの判別子へマッピングするKeleusmaErrorderive で構築したエラーを返すことにより、Wordエラーコードを報告します。第23章を参照してください。
次章では、yield を行うスクリプトを駆動します。