Rustの期待型は以下のようなデータ構造になっている。
#[derive(Copy, Clone, Debug)] pub enum Expectation<'tcx> { NoExpectation, ExpectHasType(Ty<'tcx>), ExpectCastableToType(Ty<'tcx>), ExpectRvalueLikeUnsized(Ty<'tcx>), }
このように4つのコンストラクタを持つ Expectation だが、最も大きな働きをしているのは NoExpectation と ExpectHasType であり、ほぼ 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 な式を期待しているという点である。 (&x で x が左辺値なら x は !Sized かもしれない)
ExpectRvalueLikeUnsized は to_option によって取り出される。これを調べると、 ExpectRvalueLikeUnsized は以下の用途にしか使われていないことがわかる。
- 配列リテラルの要素に対して型強制を行う。 (
[a, b, c]: ExpectRvalueLikeUnsized([T])ならaはTに型強制される)- もちろん、
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 はこの左辺・右辺と同じ型をもつことが仮定される。そのため上の 2000000000000 が i64 になるなら原則として 1000000000000 も i64 になるはずだが、そうなっていない。
これは、ここで 2000000000000 が i64 になる仕組みが、型推論ではなく、期待型により実現されているからである。ここで説明したように x as i64 は内側の x に対して ExpectCastableToType(i64) を生成する。これを受けた整数リテラルは、サフィックスを持たないリテラルの型を i64 とおく。そして ExpectCastableToType は演算子の内側には伝搬しないため、 1000000000000 は i64 とはならず、したがってデフォルトの i32 になってしまう。
後者には以下のような奇妙な例がある。
fn main() { let x = [&1] as [*const i32; 1]; }
これはコードの形に反して、 [&i32; 1] から [*const i32; 1] へのキャストではなく、 &i32 から *const i32 への型強制が行われている。
また、 ExpectCastableToType は if 式の内側に伝搬しない という特徴がある。理由はその部分のコメントにあるように、then節側で厳しすぎる推論をしてしまうことを防止するためである。