09 — ケーパビリティ

前: エフェクト (vibe の核)

English version: 09_capabilities.vibe.md

前章のエフェクトは、ハンドラで自分が実装するものでした。ファイルを読むのは そうではありません。やり方はホストが既に知っていて、問題は「あなたの プログラムにそれが許されているか」です。

それがケーパビリティです。同じ row に乗りますが、row が記録しているのは 権限で、その権限はビルド時に決まります — 呼び出しのたびに確認されるので はありません。呼び出し自体は普通の関数呼び出しのままです。

Deno のパーミッションフラグと Koka の effect system を合成したもの、と 考えてください。

権限はシグネチャの一部

fn greet(name: String) -> Unit with Console {
  println("hi \{name}")
}

fn main with Console {
  greet("vibe")
}
hi vibe

greet は端末に書くので with Console と宣言します。maingreet を 呼ぶので main も宣言します。ケーパビリティが main に勝手に現れることは なく、呼び出しから推論された上で、あなたが書いたものと突き合わされます。 シグネチャに書き忘れた関数はコンパイルされません。

Console が端末のケーパビリティです。Stdin / Stdout / Stderr はその 一部を指す古いラベルで、まだ受け付けられます。with Console はこれらを 覆いますが、逆は成り立ちません。狭い方を求めれば狭い方が来ます。

// skip: `with Stdout` は `Console::` の操作に届かない
fn main with Stdout {
  Console::write_stream("x")
}
effect row mismatch for 'main': missing { Console::write_stream }
(declared { Stdout }, requires { Console::write_stream, Stdout })
hint: add 'with Console::write_stream + Stdout' to 'main'

withallows は別の節

シグネチャは「何を発行するか」と「何を認可されているか」に分けられます。

fn main with () allows Console {
  println("authority is a separate clause")
}
authority is a separate clause

with () は空の row で、代数的なものは何もありません。allows Console が 権限です。1章の裸の fn main with Console は、同じものの短い書き方です。

分割形を書いたら、ケーパビリティは allows に置く必要があります。with に 置くと、どちらの節に属するかを教えられます。

// skip: 分割シグネチャの `with` にケーパビリティを置いた例
fn main() -> Int with Console allows Fs::read_file? {
  0
}
`Console` is a capability effect and must appear in the `allows` clause,
not `with` (ADR-0088, #1345)

権限は操作ごとのままです。allows Console::write_streamConsole::read_stream を許可しません — 表示してよいプログラムが、それに よって端末を読む権利まで得ることはありません。

省略可能なケーパビリティ: perform?

allows の項目に付く ? は「省略可能」を表します。ホストがそれを許可した かどうかに関わらず、プログラムは走れます。対応する perform? Fs::read_file("p")Attempt を返します — Granted / NotGranted / Errored

非対話コンパイラには build/apply の grant 情報がないため、未解決の optional capability は codegen 前に NotGranted へ固定されます。operation とその引数は 評価されません。

fn main() -> Int with () allows Console + Fs::read_file? {
  let a = perform? Fs::read_file("config.json")
  match a {
    NotGranted => 0,
    Errored(_) => 1,
    Granted(_) => 2
  }
}
0

固定済み解決表からの lowering は linear / wasm-gc の両 backend で共通です。 --allow-*、BindingLock、対話 preflight を production に接続する作業は #2332 に残り、 それまでは production compile が Granted / Errored を選ぶことはありません。

2種類の見分け方

どちらも row に乗り、綴りがどちらかを示します。

| | 例 | 書き方 | 実装するのは | |---|---|---|---| | 代数的エフェクト | Ask::Value | performhandle | あなた | | ケーパビリティ | Fs::read_file | 普通の呼び出し | ホスト |

Effect::CamelCase は perform する操作、Effect::snake_case は呼ぶ関数。 これが規則で、Fs::read_file(p) が権限を要するのに普通の呼び出しに見える 理由でもあります。

拒否すると実際に何が起きるか

--allow-* がビルド時に grant セットを決め、拒否されたケーパビリティは const-fold と DCE で成果物から消えます — それを必要としたコードは、 到達不能になるのではなく wasm に入りません。Http を一度も得ないプログラム はネットワークのコードを配布せず、ネットワーク可能なランタイムも要求しません (feature levels)。

起動時、ホストが許可しなかった必須ケーパビリティがあれば main の前に 中断し、許可するはずだったフラグの名前を告げます。

次: Option とレールウェイ