20 — 落とし穴 (実測)

前: wasm をターゲットにする

English version: 20_pitfalls.vibe.md (canonical)

ここに挙げるのは、人の午後を 1 つ潰す規則である。どれも現行のコンパイラで 実測した。全一覧は docs/cheatsheet.md にある。 この章は最初に噛みつくものだけ。

handle の適格性は型システムではない

handle が、それが覆う perform をすべて見られない場合、プログラムは 型検査を通ってもコンパイルに失敗しうる。呼び出しが「見える」条件は 2 つ。 callee の型が effect row を持っているか、handle がもともと見通せる callee であるか — トップレベル fn、それを別名で束縛したもの、あるいは handled body の内側で宣言されたクロージャ。

したがって local 束縛や引数を経由した呼び出しも、その型が row を持って いれば通る。local 束縛であること自体が問題なのではない。失敗するのは handled body の外で宣言された row を持たないクロージャで、その 呼び出しが何を perform するかを handle に伝えるものが無い。row を付けるか、 let を body の内側へ移す — 診断はその両方を挙げる。実測した全一覧は docs/cheatsheet.md にある。

// skip: 適格性による拒否 — 見せたいのは診断であって実行ではない
effect Ask {
  Get() -> Int
}

fn main with Exception {
  let bump = (x: Int) -> Int {
    x + 1
  }
  let n = handle {
    bump(perform Ask::Get())
  } with Ask {
    Get() => resume(0)
  }
  ()
}
handle of effect 'Ask' cannot be compiled here: this handle cannot see what
one call in its body performs (here: the call to 'bump'). Make that call
visible -- declare 'bump' as a top-level `fn`, give the binding or parameter
it arrives through an effect row (`with Ask`), or move its `let` inside the
handled body. Moving the `handle` into the function that performs works too.
(ADR-0076 evidence-passing migration.)

bump に effect row が無いことに注意。リテラルに with Ask を付けると、 それはメッセージが挙げる4つの直し方のひとつなのでコンパイルが通る — そこが 要点で、束縛に付いた row が perform を見えるようにしている。bump を トップレベルの fn に持ち上げるのも同じく有効。

Int の幅はタグビットに従う

Int63-bit (タグビット 1 本、ADR-0105)。リテラルの最大は 4611686018427387903max + 1 はどのバックエンドでも -4611686018427387904 に wrap する。まだ 2^61-1 / 62-bit と書いてある 文章は古い。

文字列補間にはレンダラが要る

\{x} で補間するユーザー struct には derive(Show)fn T::to_string(v) -> String が要る。スカラー・Option・タプル・配列は 既にレンダリングされる。Show が無いとかつてはポインタが表示されたが、 今はエラー (#1445)。

s[i] は String ではなくバイト

String は byte string。s[i] はそのオフセットのバイトで、Int'A' == 65。1 バイトの String が欲しければ String::from_char_code(s[i]) — これはバイト書き込みで、別名 String::from_byte (#2203) — かスライス。 そのバイトが文字そのものなのは ASCII のときだけ。

トップレベルは宣言だけ

トップレベルの裸の式は拒否される (ADR-0069)。fn maintest ブロックに 入れること。

// skip: ADR-0069 — ファイルは宣言の並びである
1 + 2
top-level expressions are not allowed; move it into fn main (ADR-0069)

test / bench は文字列を取るか何も取らないか

test { }test "name" { } は通る。test foo { } (裸の識別子) は 通らない。

ErrorException

effect は Exception。effect の綴りとしての Error は deprecated (ADR-0085)。古い row では Error操作の修飾子として今も見えるが、 新しく増やさないこと。

配列の ==

== は値で比較する。要素がスカラーでない配列も、関数の戻り値として来た 配列も、空リテラルも — 注釈の有無によらず — そうなる。注釈の無い let xs = [] は、それを埋める Array::push から要素型を受け取る (#2157)。 ただし push する値が自分で型を語る場合に限る — リテラル、リテラルだけ からなる配列・tuple・struct、両分岐が一致する if がそれにあたる。

名前や呼び出しの結果を push した束縛には要素型が付かず、両側とも非空になった 状態で比較すると、答えを返さず実行時に失敗する。let xs: Array[Int] = [] が その解決策。型引数を取る struct が「自分で型を語る」と見なされるのは、 == が内容で比較する型引数のときだけ — Box[Int] / Box[Bool] / Box[Unit] / Box[String] は解決し、Box[Double] / Box[Bytes] と 配列や struct の型引数は解決しない。

知っておく価値のある境界は Eq の witness を持たない generic な T で、 こちらは不意打ちではなくコンパイルエラーになる — no impl Eq for Array[Int]`。等価性 を参照。

演算子で始まる行は前の行の続き

次の行が演算子で始まるとき、改行は式を終わらせない。ブロックの最終値の つもりで書いた負のリテラルが、上の行に貼り付く:

// skip: 出る診断を見せるための例 — `-1` は `println(...) - 1` とパースされる
fn main with Console {
  let v = {
    println("failing")
    -1
  }
  println("\{v}")
}
line 3:5: type mismatch in '-': operands must be Int or Double (the left operand is Unit -- a `-` at the start of a line continues the previous line's expression instead of starting a new one; parenthesize the negated value, e.g. `(-1)` or `(-x)`, or bind it with `let` if you meant a negative value)

(-1) と書くか、先に束縛する。match 腕の直接の本体としての負リテラル (None => -1) は問題ない。#2206 以降、診断は上に引用したとおり行継続と 修正方法を名指しする。位置は引き続き結合された式の先頭を指す。

fn はキーワード

let fn = 1 は parse error。r#fn という逃げ道は無い。束縛の名前を 変えること。

for は常に集めるわけではない

let xs = for x in arr { x * 2 }Array なら通る。同じ位置に pull イテレータを置くと位置付きのエラーになる。ArrayBuilder で溜めること。

次: 付録