ファイルのimportとRust連携
別のNagiファイルは引用符付きの相対パスで読み込みます。import "ファイル名" as 名前でmodule名を付けるか、from "ファイル名" import 定義名で関数・class・enumを選べます。Rustの関数を使う場合は、Nagiで引数・戻り値の型を宣言し、ビルド時にRustファイルを指定します。
以下のmodule名・fromの別名は、Nagi 0.1.8から利用できます。
このページの目次
Nagiファイルを分割する
同じディレクトリに次の2つのファイルを作ります。models.nagiにはclassを書きます。
class Item:
name: str
count: i64
app.nagiで読み込みます。
import "models.nagi"
def main():
item = Item(name="Nagi", count=42)
print(item.name)
print(item.count)
nagic run app.nagi
Nagiと42が表示されます。パスはimportを書いたファイルのディレクトリを基準にします。Highは.nagi、Lowは.lowを読み込めます。Lowではimportの末尾に;を置けます。
この従来のimportは、依存先も含めた定義を同じ名前空間に読み込みます。組み込み関数も従来どおり使えます。
module名と定義の別名
orders.nagiに次の定義を置きます。
class Order:
amount: i64
def score(order: Order) -> i64:
return order.amount
app.nagiではmodule名とclassの別名を使えます。
import "orders.nagi" as orders
from "orders.nagi" import Order as SavedOrder
def main():
order: SavedOrder = orders.Order(amount=42)
score = orders.score
print(score(order))
orders.OrderとSavedOrderは同じ型です。orders.score(order)で直接呼び出すこともできます。型引数・フィールド型・nullableでもList[orders.Order]やorders.Order?を使えます。別ファイルで定義した同名のOrderは別の型になり、混ぜて渡すと型エラーになります。
module名で見えるのは、そのファイル自身が定義した関数・class・enumです。importした名前は自動で再公開しません。from "orders.nagi" import Orderのように別名を省略することもできます。複数の定義はfrom "orders.nagi" import Order as SavedOrder, scoreのように,で選べます。末尾の余分な,は付けません。fromとasはimportの文脈だけで解釈し、関数や変数の名前にも使えます。関数内でmodule名と同じローカル名を使った場合は、現在のローカル変数の規則に従います。classのmethod呼び出しには対応していません。
同じ実ファイルは、複数のmodule名・fromの別名・従来のimportを使っても1回だけ読み込みます。循環するimport、見つからないファイル、HighとLowの混在はエラーです。存在しない定義のfrom importや、同じ場所で異なる定義を同じ名前にするimportは、そのimport文でエラーになります。引用符なしのimportは登録済みのstd.http.server、std.actor、std.result等を対象にします。
0.2.0 SF01のstd.authはrequest-bound AuthScopeとGrant[P]を使います。旧Principalと無期限issuerは廃止しました。認証・認可に型・lease・trusted adapterの責務、移行に差分があります。サンプルは標準Policy経路を使う有限credentialの検証例で、productionの暗号検証や永続Sessionを実装したとは扱いません。
LowとRustで同じ定義を使う
Lowでもimport "orders.low" as orders;とfrom "orders.low" import Order as SavedOrder;を使えます。Highから生成したLowにはmoduleと定義のIDを残すため、再parseや手書きLowとの統合でも型の区別を保ちます。
手書きLowでrootのmodule名ordersにある関数を差し替える場合は、@replace generated::orders::scoreで対象を指定します。従来の@replace generated::scoreも使えます。引数・戻り値・asyncの一致などの条件はLowの差し替えと同じです。
Rustのアダプターからは、rootで読み込んだmoduleのclassをsuper::orders::Order、fromの別名をsuper::SavedOrderで参照します。両方とも同じ生成型を指します。従来の平坦なimportのsuper::Itemも使えます。内部の名前は衝突を避けて生成し、JSONのフィールド名やSQLの列名には元の名前を残します。moduleの実行例では、同名classと関数の別名を試せます。
読み込みの上限とエラー位置
最大128ファイル、深さ64、合計8 MB、1ファイル2 MBです。型検査とLow統合のエラーは、元のファイル名と行番号を表示します。生成されたRustでのエラーも対応するNagi・Lowの行を先に表示します。対応する行が分からない場合や手書きRustのエラーは、Rustの位置を表示します。列位置の対応は未実装です。
テキストファイルを埋め込む
include_text("index.html")は、ソースに隣接するUTF-8ファイルをビルド時に読み込み、strとして埋め込みます。パスは文字列リテラルで指定してください。配布したアプリを実行するときには、元のファイルは不要です。
標準HTTP libraryを読み込む
std.http.serverとstd.actorはNagi 0.1.8から使える標準ライブラリです。以下の応答factory例は0.2.0のContext-first APIを使います。
import std.http.server as http
from std.http.server import Request, Response, Status as Code
def greeting(request: Request) -> Result[Response, Error]:
context = http.response_context(view(request))
return http.text(context, Code.OK, "Hello, Nagi!")
この例はrequest-borrowed contextを使うresponse factory断片で、listenerを起動しません。response予算、rate予算、各routeのrate policyを設定する起動手順はHTTP APIリファレンスを参照してください。
標準moduleはコンパイラと一緒に提供する登録済みのlibraryです。カレントディレクトリの同名ファイルやエディターのbufferで差し替えません。module importにはasを付け、fromでは必要な定義だけを選べます。importするだけでlistenerやworkerは起動しません。生成Lowにも標準定義のIDを残し、標準型のaliasは同じnative型を指します。
Requestなどのresource型は型の資料を参照してください。App[State, E]はAppごとにstateとエラー変換を持ち、Dbなしでも使えます。routeは名前付きasync handlerまたはそのローカルaliasを受け取り、route_mappedはroute専用の変換を指定します。一般のasync関数値を引数やclassへ保存する制約は変わりません。型と所有権はcheckで、handler futureのSend + 'staticと共有StateのSend + SyncはRust buildで検査します。起動・応答・headersの操作はHTTPにあります。
標準actor libraryを読み込む
actorも同じimportの規則を使います。
import std.actor as actor
from std.actor import Actor as Worker, CallError
Worker[M, R, E]とactor.Actor[M, R, E]は同じnative型です。Supervisor[C]へ名前付きasyncのfactoryとhandlerを登録し、Turn[S, R, E]で次の状態と返信を返します。メッセージ・返信・業務エラーは容量を数えられる所有値が必要で、Map・view・shared・opaque resourceを含められません。actor・APIリファレンス・サンプルに実際の署名と手順があります。
標準の値転送操作を読み込む
std.ownership.moveはNagi 0.1.11で公開済みです。利用するcompilerが0.1.11以降であることを確認してください。module名と定義の別名のどちらでも呼び出せます。次の完全な例はNagiを表示します。
import std.ownership as ownership
from std.ownership import move as transfer
def main():
first = "Nagi"
second = ownership.move(first)
third = transfer(second)
print(third)
所有する非Copyローカルそのものの代入には明示操作を使います。新値生成やCopy代入、引数・return・field/index等の既存consume規則は維持します。moveは予約語ではなく、同名のユーザー関数は通常の関数です。標準操作自体を第一級関数値にすることはできません。引数は一つ、型は入力から推論し、明示型引数は受け付けません。借用やFutureの制約と正確な移行範囲は所有権を参照してください。
Rustの関数を呼ぶ
次をapp.nagiに保存します。@rustは呼び出すRustの関数を指定し、extern defはその引数・戻り値の型を宣言します。宣言に本体や末尾の:は付けません。
@rust("native::text_bytes")
extern def text_bytes(text: view[str]) -> i64
def main():
text = "Nagi"
print(text_bytes(view(text)))
print(text)
同じディレクトリのnative.rsにRustの関数を書きます。
pub fn text_bytes(text: &str) -> i64 {
text.len() as i64
}
nagic run app.nagi --rust native.rs
4とNagiが表示されます。文字列はview[str]で借りているので、呼び出し後も使えます。--rustはRustファイルをnativeというモジュールとして組み込みます。関数はpubで公開し、宣言した型に合わせてください。たとえばNagiのstrはRustのString、view[str]は&strです。
Lowではextern fn ...;と書きます。Rustのasync関数にはextern async def(Lowではextern async fn)を使い、呼び出し側でawaitします。Rustの関数のパスはnative::から始まる識別子の列です。extern関数は、viewを返す宣言やHTTP属性には対応していません。
Axumの見積APIは、HTTPのルーティングとJSONの読み取りをRustに、型付きの検証と計算をNagiに置く例です。Rustアダプターから名前を指定してNagiのasync関数をawaitし、Resultを受け取ります。そのNagi関数からもRustのasync処理をawaitします。これは生成された特定の関数を直接呼ぶ方法で、任意のasync関数値をextern引数で渡す機能ではありません。
Rustのcrateを使う
Cargoの依存は--rust-dep NAME=VERSIONで追加します。たとえばserde_jsonを使うアダプターでは、次のように指定します。
nagic run app.nagi --rust native.rs --rust-dep serde_json=1.0
初回はCargoが依存を取得するため、通常はネットワーク接続が必要です。ローカルcrateのpath、features、依存名とpackage名を分けるpackage指定には、nagi.tomlの依存tableを使います。ローカルRustライブラリのサンプルは、独立したcrateをアダプターから呼び、Rustの構造体・エラーをNagiのclass・Errorへ変換します。生成されたNagiの名前への参照はアダプターにまとめ、独立したcrateにはそのcrate自身の型とAPIを残します。crateを使う完全な例はリポジトリのRust連携サンプルにあります。入口・Rustファイル・依存を毎回指定せずに使う場合は、nagi.tomlとプロジェクトに保存してください。CLIとVS Codeで同じ設定を使えます。
Nagiのcheckは、宣言した型と呼び出し、所有権、借用を検査します。check・lower・symbolsはCargoを呼ばず、依存を取得しません。Rustの本体やcrateのAPIは検査しません。宣言とRustの実装が一致するかどうかはbuildで検査します。登録済み標準resourceは対応するnative型を使えます。それ以外のRust固有の型は、Rust側で数値・str・List・class・Resultなどへ変換してから渡してください。Rust側から生成したNagiのclassを参照する場合は、rootのimportに合わせてsuper::型名やsuper::module名::型名を使います。
生成したCargo.lockを保持してcargo build --locked --manifest-path build/app/Cargo.tomlを実行すると、同じ依存の解決を再利用できます。通常のnagic buildは生成したプロジェクトへのcargo build --releaseを実行し、既存のlockを保持します。nagic build --lockedは未対応です。lockはローカルcrateのソース内容を固定しません。
この連携は同じRustビルド内で関数を呼び出します。安定したC ABIや、実行時にDLLを読み込む機能は未対応です。
Rustの型不一致は、対応するNagi宣言や文のファイル・行に表示します。生成Rustの詳細が必要な場合はnagic build app.nagi --rust-diagnosticsを使います。手書きRustや依存crateの診断はRust位置のまま残ります。unsafe、panic、trait、暗号やpolicyの正しさはexternの型宣言だけでは検査できません。
共通コードを複数アプリで使う例、Rust側へNagiの関数を渡す例はライブラリとRustの資産とサンプル一覧にあります。現在の対応と、resource・ランタイム選択などの追加案はライブラリの設計に整理しています。