Rust unsize期待型とキャスト期待型

Rustの期待型は以下のようなデータ構造になっている。

#[derive(Copy, Clone, Debug)]
pub enum Expectation<'tcx> {
    NoExpectation,
    ExpectHasType(Ty<'tcx>),
    ExpectCastableToType(Ty<'tcx>),
    ExpectRvalueLikeUnsized(Ty<'tcx>),
}

このように4つのコンストラクタを持つ Expectation だが、最も大きな働きをしているのは NoExpectationExpectHasType であり、ほぼ Option<Ty> と思って問題ない。

この記事ではまず、残りの2つの動作について把握する。これは ExpectHasType よりも弱い期待をするもので、ごく限定された場面でのみ生成される。

unsize期待型

ExpectRvalueLikeUnsized(U) は、その式の型が T: Unsize<U> であるような T であることを期待するものである。

この期待型は rvalue_hint によってのみ生成される。この関数は、

  • [T], str, Trait 型に対しては ExpectRvalueLikeUnsized
  • それ以外の型に対しては ExpectHasType

を期待する。この処理は以下の部分で行われる。

  • 関数呼び出し引数の推論された期待型を推論するとき。
  • 関数呼び出し引数を型強制するとき。 ([T], str, Trait に対しては型強制は実行されない)
  • box x 式の期待型が Box<T> だったとき。
  • &x/&mut x 式の x が左辺値で、期待型が &'a T/&'a mut T だったとき

これらに共通するのは、これらが全て Sized な式を期待しているという点である。 (&xx が左辺値なら x!Sized かもしれない)

ExpectRvalueLikeUnsizedto_option によって取り出される。これを調べると、 ExpectRvalueLikeUnsized は以下の用途にしか使われていないことがわかる。

  • 配列リテラルの要素に対して型強制を行う。 ([a, b, c]: ExpectRvalueLikeUnsized([T]) なら aT に型強制される)
    • もちろん、 ExpectRvalueLikeUnsized 以外の期待型についてもこれは行われる。

例えば以下の例では、配列の要素に対して型強制が行われている。

fn main() {
    let x : Box<[*const i32]> = Box::new([&1]);
}

キャスト期待型

ExpectCastableToType(T) は、その式の型が x as T で変換可能であることを期待するものである。

これは as 式の型検査に対してのみ生成される。逆に、これが利用されるのは以下の場面のみである。

前者については、以下の例を見るとわかる。

fn main() {
    println!("{}", 2000000000000 as i64); // 2000000000000
    println!("{}", (1000000000000 + 1000000000000) as i64); // -1454759936
}

これは以前の記事で紹介した仕組みだけでは説明できない。以前の記事で紹介した仕組みにより、 1000000000000 + 1000000000000 はこの左辺・右辺と同じ型をもつことが仮定される。そのため上の 2000000000000i64 になるなら原則として 1000000000000i64 になるはずだが、そうなっていない。

これは、ここで 2000000000000i64 になる仕組みが、型推論ではなく、期待型により実現されているからである。ここで説明したように x as i64 は内側の x に対して ExpectCastableToType(i64) を生成する。これを受けた整数リテラルは、サフィックスを持たないリテラルの型を i64 とおく。そして ExpectCastableToType演算子の内側には伝搬しないため、 1000000000000i64 とはならず、したがってデフォルトの i32 になってしまう。

後者には以下のような奇妙な例がある。

fn main() {
    let x = [&1] as [*const i32; 1];
}

これはコードの形に反して、 [&i32; 1] から [*const i32; 1] へのキャストではなく、 &i32 から *const i32 への型強制が行われている。

また、 ExpectCastableToTypeif 式の内側に伝搬しない という特徴がある。理由はその部分のコメントにあるように、then節側で厳しすぎる推論をしてしまうことを防止するためである。