テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(rust report) mut&は何故ないのか?

分かる人がいたら教えてくれシリーズ. (Twitter/Mailでマサカリを投げてきて) 以下では言い切ってるところも多いが, ほとんどの場合, おれの推察である.

まず議題となるコード. これがなぜダメなのかおれなりに考える. このコードは, vから先頭2つをbにreadした時にvはどういう状態になるか?を確かめる過程で生まれた. 結論としては[2,3,4]になる. それがreadの挙動らしい.

use std::io::Read;
fn main() {
    let mut v: Vec<u8> = vec![0,1,2,3,4];
    let mut b = vec![0;2];
    let vv: &mut[u8] = &mut v[..];
    vv.read(&mut b).unwrap(); // これはコンパイル通らない
}

このコードが謎なのは, readの型が

pub trait Read {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;

なのに,

error: no method named `read` found for type `&mut [u8]` in the current scope
 --> src/main.rs:8:8
  |
8 |     vv.read(&mut b).unwrap();
  |        ^^^^
  |
  = note: the method `read` exists but the following trait bounds were not satisfied: `[u8] : std::io::Read`

と怒られることだ. vvの型は&mut[u8]なので, readはコンパイルが通っても良さそうなものだが.

ちょっと, vvの作り方を変えよう.

use std::io::Read;
fn main() {
    let mut v: Vec<u8> = vec![0,1,2,3,4];
    let mut b = vec![0;2];
    let mut vv: &[u8] = &v[..];
    vv.read(&mut b).unwrap();
}

こうするとうまく行く. 今度はvvの型は&[u8]だが, variable自体がmutになっている点がさきほどと異なる.

なぜか?

試しに第二引数を第一引数と同様の方式で作ってみよう.

use std::io::Read;
fn main() {
    let mut v: Vec<u8> = vec![0,1,2,3,4];
    let mut b = vec![0;2];
    let mut vv: &[u8] = &v[..];
    let mut bb: &[u8] = &b[..];
    vv.read(bb).unwrap(); // これはコンパイル通らない
}
error[E0308]: mismatched types
 --> src/main.rs:9:13
  |
9 |     vv.read(bb).unwrap();
  |             ^^ types differ in mutability
  |
  = note: expected type `&mut [u8]`
  = note:    found type `&[u8]`

今度は, &mut [u8]が要求されているが, &[u8]を渡されたと怒られた.

これらは, readが実はread(self: mut &[u8], &mut [u8])というシグネチャなのだと考えると理解出来る.

しかし何らかの理由で, Rustにはmut &という書き方は存在しない. これはおそらく, この2つがRustの型システムの中で全く別モノとして扱われていて混同することがないからだ. 言い換えると, 片方が必要な時にもう片方がたまたまコンパイルを通ってしまうことがないからだ. 実際にこれらのエラーメッセージは明らかに, &mut [u8], &[u8]というものを型として扱っている. Rustがmutabilityや所有権/Borrowを型として認識しているという証拠に, BorrowやBorrowMut, Fn/FnMut/FnOnceなどが定義されていることが挙げられる.

メモリレイアウトの観点からそれぞれのletが何を意味してるのかを考えてみよう.

let mut v: Vec<u8> = vec![0,1,2,3,4];

は, ヒープにある領域(配列)とスタックフレームにある(ヒープ領域へのポインタ, capacity, length)のファットポインタについてrwする権限を持つということ.

let mut vv: &[u8] = &v[..];

は, そのヒープ領域に対するread onlyなファットポインタ(ポインタ, length)がwritableである.

let vv: &mut[u8] = &mut v[..];

は, そのヒープ領域に対するwritableなファットポインタがread onlyである.

つまり, どこがwritableかがお互いに排反だから, あらゆるケースにおいて片方がもう片方を代替することがないということなのだ.

おそらく通常のケースにおいて, let mut = &の形をとることには意味がない. 意味があるのはVecやsliceを扱う時だけであり, これらの型に対してRustがスタック上のメモリレイアウトを特殊扱いしているということだ.

let mut = &がスタック上のオブジェクトに対してwritableであるということは,

fn main() {
    let v: Vec<u8> = vec![0,1,2,3,4];
    let mut vv = &v;
}

コンパイルが通ることでも分かる. vがmutでないにも関わらず, vvはmutで定義出来てしまっている. これならば明らかに, vのメモリはread onlyに守られていて, vvの部分だけがwritableであるということが分かる.

いずれにしろ, mut &Tという型を書けるようにした方がわかりやすいのではないかと思う.