章一覧

借用と参照

資源を move せずに読み、必要なら書き込みます。

8.1 読み取りと書き込み

&x は読み取りの借用、&mut x は変更のための借用です。関数の引数にも同じ記号を付けます。

ch08-borrow.taut
import std::prelude::Affine

fn length(&v: Vec(i64)): u64 {
  Vec::len(&v)
}

fn append_one(&mut v: Vec(i64)): () {
  Vec::push(&mut v, 1)
}

var v := Vec::new()
append_one(&mut v)
let n := length(&v)
perform show("len", n)
実行結果
・ length
・ append_one

len = 1
len = 1

length は Vec を消費しません。append_one は既存の Vec を変更します。&mut を取れるのは var だけです。

8.2 借用を返す「view」

借用を返す関数を使うと、大きな値をコピーせずに、その一部を参照できます。

ch08-view.taut
import std::prelude::Affine

fn first(&v: Vec(i64)): &i64 ! {out_of_bounds} {
  Vec::index(&v, 0)
}

var values := Vec::new()
Vec::push(&mut values, 7)

let head := handle {
  let &r := first(&values)
  r
} with {
  out_of_bounds() => 0
}
perform show("head", head)
実行結果
・ first

head = 7
head = 7

first は、先頭の要素への借用を返します。要素がない可能性は、out_of_bounds という effect で表されています(! {out_of_bounds} が effect の行です)。この effect は handle で処理しないと、実行時に「unhandled effect」で止まります。この例ではハンドラが 0 を返すので、空の場合の代わりの値も用意できます。

8.3 借用の範囲を意識する

Tauto の借用には、強い制限があります。借用を任意の構造体に入れたり、外へ持ち出す閉包に捕まえさせたりすることは許されません。let &r := ... で束縛した借用では、現在のブロックの終わりまで、持ち主が凍結されます。凍結は字句的で、最後の使用で解けるわけではありません。

ただし、結果が複製できる型への借用(&i64 など)は、i64 そのものと同じに扱われ、凍結は起こりません。凍結を見るには、複製できない要素への借用を使います。

ch08-freeze.taut
import std::prelude::Affine

fn first_row(&m: Vec(Vec(i64))): &Vec(i64) ! {out_of_bounds} {
  Vec::index(&m, 0)
}

var m := Vec::new()
var row := Vec::new()
Vec::push(&mut row, 1)
Vec::push(&mut m, row)

let n := handle {
  let &r := first_row(&m)
  Vec::push(&mut m, Vec::new())
  Vec::len(&r)
} with {
  out_of_bounds() => 0
}
perform show("n", n)
✗ エラー
・ first_row

error 14:13: m is frozen by r

r が生きているブロックの中で m を変更しようとしているので、検査で止まります。

ch08-freeze.taut:14:12: use error: m is frozen by r

Vec::push(&mut m, Vec::new()) を handle の外(借用のブロックのあと)に移せば、通ります。

この方式は、「借用が使われなくなった最後の行で凍結を解く」方式より保守的です。その代わり、借用の境界がソースから読み取れます。

演習 8.1:fn first(&v: Vec(i64)): &i64 ... に out_of_bounds が必要な理由を説明してください。