Rustにおける演算子の型推論の特殊ルール
概要: 原則として x + y
は ::std::ops::Add(x, y)
の構文糖衣であるが、型推論で特別扱いされる。
演算子の脱糖
演算子の脱糖は型推論の後、HIRからMIRへの変換のタイミングで行われる。原則として x + y
は ::std::ops::Add(x, y)
の構文糖衣である。これは *x
/x[i]
以外の他の演算子についても同様である。しかし型推論では以下のような特殊扱いがある。
fn main() { use std::ops::Add; let x : i8 = 1 + 1; // OK let x : i8 = Add::add(1, 1); // Error }
下側のコードがエラーになる理由
Rustの演算子はオーバーロード可能であり、型の制約が少ない。具体的には「左辺と右辺の型から、計算結果の型が一意に決まる」ということだけが要請される。そのため型 A
と型 B
の値を足して型 C
の値ができることがありえる。例えば、以下のような足し算を定義することができる。
fn main() { use std::ops::Add; struct A; struct B; impl Add<B> for A { type Output = i8; fn add(self, other: B) -> Self::Output { 42 } } let x : i8 = 1i8 + 1i8; // OK let x : i8 = A + B; // OK }
つまり、外側の型 i8
から、内側の型を決めることは基本的にできない。演算子に限らず、戻り値型が <A as Add<B>>::Output
のような射影型になっているときは、型はボトムアップにしか伝搬しない。
つまり、 Add::add(1, 1) : i8
から 1 : i8
は推論されない。この型情報の不足から、 1: {integer}
だけが判明する。これは後でデフォルトである i32
と推論されるが、すると Add::add(1i32, 1i32) : i32
であり型が一致しない。
上側のコードがエラーにならない理由
lhs + rhs
で、 lhs
, rhs
がある特定の型の場合に、Rustは「lhs
, rhs
, lhs + rhs
の型は全て同じ」という制約を追加する。これにより自動的に内側の 1
が i8
であることが判明する。
演算子ごとの動作の違い
二項演算子は以下の4種類に分類される。それぞれ、特定の条件下で型に対して追加の仮定をおく。
- 短絡回路演算子:
||
,&&
- 常に、左辺/右辺/戻り値は
bool
である。
- 常に、左辺/右辺/戻り値は
- シフト演算子:
<<
,>>
- 両辺がともに整数型であるとき、左辺の型と戻り値の型は等しい。
- 数学演算子:
+
,-
,*
,/
,%
- 両辺がともに整数型であるか、両辺がともに浮動小数点数型であるとき、左辺の型と右辺の型と戻り値の型は等しい。
- ビット演算子:
|
,&
,^
- 両辺がともに整数型であるか、両辺がともに浮動小数点数型であるか、両辺がともに
bool
であるとき、左辺の型と右辺の型と戻り値の型は等しい。
- 両辺がともに整数型であるか、両辺がともに浮動小数点数型であるか、両辺がともに
- 比較演算子:
==
,!=
,<
,<=
,>
,>=
- 両辺がともにスカラー型であるとき、左辺の型と右辺の型は等しく、戻り値の型は
bool
に等しい。
- 両辺がともにスカラー型であるとき、左辺の型と右辺の型は等しく、戻り値の型は
ただし、スカラー型とは、 bool
/char
/整数型/浮動小数点数型/関数定義/関数ポインタ/生ポインタ のいずれかである。
「整数型である」「浮動小数点数型である」というのは、 i32
や usize
のような特定の型のほかに、 {integer}
のような推論型も含む。
単項演算子の場合
3つある単項演算子 *
/!
/-
のうち、 !
と-
も似たような動作をする。ただし以下の違いがある:
!
は、被演算子が整数または bool
のとき、 -
は、被演算子が整数または浮動小数点数のときに、組込み演算子とみなされる。このとき戻り値の型は被演算子の型と等しいと仮定される。