teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

サブタイピングの型推論について追記。

2018/07/15 02:05

投稿

qnighy
qnighy

スコア210

answer CHANGED
@@ -54,6 +54,47 @@
54
54
 
55
55
  なお、こういう事情がありますから、基本的に再借用が抑制されて嬉しいパターンはあまり多くないです。ぼくが知っている唯一の例は[連結リストの例](https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/)です。
56
56
 
57
+ ## 追記: サブタイプの型推論について
58
+
59
+ 「実引数が仮引数のサブタイプであるという制約を追加する。」についてより詳しく説明します。結論からいうと、これをした時点で `T` が `&mut i32` の形であること、そして**そのライフタイムがもとのライフタイムより同じか小さい**ことが確定します。
60
+
61
+ まず、Rustの型推論は単相Hindley-Milnerに基づいているので、軽く復習しておきます。[Niko Matsakis氏のブログ](http://smallcultfollowing.com/babysteps/blog/2017/03/25/unification-in-chalk-part-1/)でも使われている(比較的一般的と思われる)記法として、型変数を `?T` と表記することにします。
62
+
63
+ Hindley-Milnerでは、 `type1 == type2` という制約が追加されるごとに、現時点でわかっている最も一般的な解(most general unifier; mgu)を求めていきます。
64
+
65
+ たとえば、`?T`, `?U`, `?V` が未解決の型変数として、 `(Vec<?T>, Option<?U>) == (?U, Option<?V>)` という制約が追加されると、
66
+
67
+ - `(Vec<?T>, Option<?U>) == (?U, Option<?V>)` ⇔ `Vec<?T> == ?U` かつ `Option<?U> == Option<?V>` なのでこれらを再帰的に解く
68
+ - `Vec<?T> == ?U` なので `?U` に `Vec<?T>` を代入する
69
+ - `Option<?U> == Option<?V>` ⇔ `Option<Vec<?T>> == Option<?V>` ⇔ `Vec<?T> == ?V` なので `?V` に `Vec<?T>` を代入する
70
+
71
+ というようにして、「`?U` に `Vec<?T>` を代入し、 `?V` に `Vec<?T>` を代入する」のが最も一般的な解であることがわかります。 (`?T` の中身は依然不明なので、その後追加される制約で解決されることが期待されます。)
72
+
73
+ 最も基本的なHindley-Milnerでは以上のようにして等号を再帰的に解きます。では**Rustのサブタイピング**が入った場合のHindley-Milnerを考えてみます。
74
+
75
+ この場合、 `type1 == type2` の形の制約に加えて、 `subtype <: supertype` の形の制約を考える必要があります。しかしやることは同じで、一般性を失わないように制約を分解していけばいいことになります。
76
+
77
+ では `&'a mut i32 <: ?T` という制約の場合はどうでしょうか。まず、`&mut`は組み込みの構文が与えられていますが、型システムという観点からは `RefMut<'a, T>` のような型とみなせます。 (標準ライブラリの同名の型とは別です) つまり、 `RefMut<'a, i32> <: ?T` という制約を解くことになります。
78
+
79
+ この制約から確実にわかることはなんでしょうか。Rustのサブタイピングでは生存期間以外の構造が変わることはありません。したがって `?T` が `RefMut` であることはこの時点でわかっています。つまり、新しい生存期間変数 `'b` と型変数 `?U` を導入して
80
+
81
+ `RefMut<'a, i32> <: RefMut<'b, ?U>` かつ `RefMut<'b, ?U> == ?T`
82
+
83
+ と書けることになります。あとは `RefMut` 同士のサブタイプ制約を分解するだけです。 `RefMut<'a, T>` は `'a` に対して共変で `T` に対して非変ですから、
84
+
85
+ `RefMut<'a, i32> <: RefMut<'b, ?U>` ⇔ `'a <: 'b` かつ `i32 == ?U`
86
+
87
+ となります。 `'a <: 'b` (期間の包含でいうと `'b <= 'a`) は型推論にとってはもう分解できない制約なので(そのままborrow checkerに渡される)、これで終わりです。結局、
88
+
89
+ - 新しい変数 `'b`, `?U` を導入する
90
+ - `?T == RefMut<'a, ?U>`
91
+ - `?U == i32`
92
+ - `'a <: 'b`
93
+
94
+ とするのが、この時点で最も一般的な解ということになります。
95
+
96
+ これが、前の節の「実引数が仮引数のサブタイプであるという制約を追加する。」の中で起こっていることです。
97
+
57
98
  ## おまけ
58
99
 
59
100
  > ところで `foo()` に渡す順序を変えて `foo(ry, rx)` とすると、今度は `use of moved value: 'ry'` となりました。あまり考えず、第一引数に合わせて推論しているような気もします。

1

驚くべきことに、リストのネストができないので苦し紛れをした

2018/07/15 02:05

投稿

qnighy
qnighy

スコア210

answer CHANGED
@@ -46,8 +46,8 @@
46
46
  さて、では再借用されていないのにライフタイムが縮まる問題ですが、[引数の型推論の処理](https://github.com/rust-lang/rust/blob/1.27.1/src/librustc_typeck/check/mod.rs#L2701-L2705)に答えがありそうです。ここを見ると、
47
47
 
48
48
  - まず、仮引数と実引数の型を比べて、条件次第で型強制を挿入する。
49
- - ちなみにコンパイラ内部では再借用は型強制の一種として処理されています。
49
+ - ちなみにコンパイラ内部では再借用は型強制の一種として処理されています。
50
- - [型強制のコード](https://github.com/rust-lang/rust/blob/1.27.1/src/librustc_typeck/check/coercion.rs#L208-L222)を見るとわかりますが、強制先の型が参照と判明しているときだけ再借用が検討されます。これが、ジェネリックパラメーターでは再借用されないとされる所以です。
50
+ - [型強制のコード](https://github.com/rust-lang/rust/blob/1.27.1/src/librustc_typeck/check/coercion.rs#L208-L222)を見るとわかりますが、強制先の型が参照と判明しているときだけ再借用が検討されます。これが、ジェネリックパラメーターでは再借用されないとされる所以です。
51
51
  - ↑の成否にかかわらず、実引数が仮引数の**サブタイプ**であるという制約を追加する。
52
52
 
53
53
  となっています。今回は `rx` は再借用を免れてはいますが、サブタイプされてより小さいライフタイムとみなされているのではないかと思います。