RustBeltのビルド (Windows)
概要: Coqと高階分離論理を用いたRustの検証プロジェクトであるRustBeltの論文とCoqの証明ファイルが公開されたので、とりあえずビルドしてみた。
RustBeltはopamを使うと簡単にビルドできるようだが、ここではWindowsでビルドしてみた。
環境
- 64bit Windows
- MSYS2
gitとmakeのインストール
$ pacman -S make git
Coqのインストール
CoqのWebサイト から coq-installer-8.6-x86_64.exe
をダウンロードし、実行する。今回はデフォルトのディレクトリ C:\Coq
にインストールした。
/c/Coq/bin
をPATHに追加する。例えば .zshrc
に
export PATH="${PATH}:/c/Coq/bin"
と書く。
ssreflect/mathcompのインストール
依存関係: Coq, make
ssreflect/mathcompのWebサイト から ssreflect-mathcomp-installer-1.6.1-win64.exe
をダウンロードし、実行する。Coqのインストールディレクトリと同じディレクトリ
C:\Coq
を指定する。ssreflectは %COQDIR%\lib\user-contrib\mathcomp
にインストールされる。
coq-std++のビルドとインストール
依存関係: Coq, git, make
$ git clone -b ee6200b4d74bfd06034f3cc36d1afdc309427e5c https://gitlab.mpi-sws.org/robbertkrebbers/coq-stdpp.git $ cd coq-stdpp $ make && make install
Irisのビルドとインストール
依存関係: Coq, ssreflect/mathcomp, git, make, coq-std++
$ git clone -b 398bae9d092b6568cf8d504ca98d8810979eea33 https://gitlab.mpi-sws.org/FP/iris-coq.git $ cd iris-coq $ make && make install
RustBeltのビルド
依存関係: Coq, ssreflect/mathcomp, git, make, coq-std++, iris
RustBeltのWebサイトの “RustBelt: Securing the Foundations of the Rust Programming Language.” という論文のappendixをダウンロードする。 appendix.zip
を解凍する。
$ cd appendix/lambdaRust $ make && make install
以上
RustのRFC一覧 (~0500)
概要: RustのRFCの一覧を作ろうとしたが、あまりに多いのでとりあえず0500までで公開することにした。なお、単に全てのRFCを列挙したいならばここを見ればよい。
このRFCはRustのコミュニティーが管理しているものであり、 “RFC” の元祖であるIETF RFCとは関係ない。いずれもrequest-for-commentの略である。
メタ
スタイル/慣習
- RFC 0199 似たような動作で、所有権/借用の種別だけが異なるようなメソッドの命名規則を定める
- RFC 0236
panic!
/Result
の使い分けの慣習を定義する、関連するメソッドの命名規則を定める - RFC 0240
unsafe
の慣習の整理:unsafe
関数をメソッドにする条件の定義、論理的な不変条件を壊すが未定義動作を引き起こさない関数はunsafe
ではなく*_unchecked
と命名することを定義、その他unsafe
関数の命名規則の定義 - RFC 0344 関数/メソッド名が型名に言及する際の命名規則 (
Prelude*
はRFC 0445により廃止され、Rust PR #23104により完全に不要になった) - RFC 0430 命名規則のケーシング(snake_case, CamelCase, etc.) と、
unwrap
/into_*
の命名規則を定義 - RFC 0445 拡張トレイトパターンに使うトレイトの名前を
*Ext
とする命名規則
ソースファイル処理と字句
- RFC 0063 二重インクルードによる混乱を防ぐため、
mod foo;
による外部ファイルの読み込みに制限をつける - RFC 0069 バイトリテラル
b'x'
とバイト列リテラルb"Foo"
の導入 - RFC 0090/0021 字句解析器をより単純にする
- RFC 0326 文字/文字列リテラルにおいて
\x00
-\x7F
に限定し\x80
-\xFF
を禁止 - RFC 0342
abstract
,final
,override
をキーワードとして予約する - RFC 0446 文字/文字列リテラルの
\uXXXX
/\UXXXXXXXX
を廃止し、\u{XXXXXX}
を導入 - RFC 0463 将来の互換性のために、リテラル直後の識別子の字句規則を変更
構文
- RFC 0016 属性を
let
文、ブロック、式にも使えるようにする (#![feature(stmt_expr_attributes)]
) - RFC 0049 マッチ腕に属性を使えるようにする
- RFC 0059
~T
型と~x
式を削除し、Box
/box
で置き換える - RFC 0068
*T
型を*const T
にリネーム - RFC 0071
const
/static
内でも、一定条件下でブロックを使えるようにする - RFC 0087 トレイトオブジェクトの追加の境界を
&(Foo : Send + Sync)
ではなく&(Foo + Send + Sync)
のように指定するようにする - RFC 0092
if
やfor
などのブロックと紛らわしい場面では構造体の{}
を禁止する - RFC 0132 UFCS (
<T as Trait>::sth
/<T>::sth
) の導入 - RFC 0135
where
節の導入 (一部未実装) - RFC 0160
if let
構文 - RFC 0164 スライスパターンをfeature gateにする
- RFC 0168
{mod, ..}
によりモジュール自体とその中身を同時にインポートする (RFC 0532により廃止) - RFC 0169
use baz = foo::bar;
構文を廃止しuse foo::bar as baz;
で置き換える - RFC 0179
&p
パターンを&p
と&mut p
パターンに分割し、参照のミュータビリティーに応じて使い分けるようにする - RFC 0184
foo.0
のように、タプル/タプル構造体のフィールドに整数でアクセスできるようにする - RFC 0194
#[cfg(...)]
の構文の整理 - RFC 0198
foo[]
/foo[n..m]
/foo[n..]
/foo[..n]
のためのトレイトSlice
/SliceMut
を導入 (RFC 0439により廃止:foo[]
はfoo[..]
になり、..
は[]
と独立な構文になった) - RFC 0202
[x, ..ys]
パターンを[x, ys..]
に変更 (#![feature(slice_patterns)]
/#![feature(advanced_slice_patterns)]
に注意) - RFC 0214
while let
構文 - RFC 0218 空のレコード構造体
struct A {}
を許可 - RFC 0243
e?
と(do) catch {}
構文によるエラー処理 (RFC 1859も参照。catch構文は#![feature(catch_expr)]
) - RFC 0339 バイト列リテラルの型を
&'static [u8]
から&'static [u8; N]
に変更 - RFC 0418
enum
のバリアントをレコード形式 (enum Foo { Bar {}}
) で宣言する機能を安定化 - RFC 0438 型における
&
と+
の優先順位を変更し、&(Foo + Bar)
のように書くようにする - RFC 0450 RFC 0184 (
foo.0
), RFC 0160 (if let
), RFC 0214 (while let
) の安定化,TupleN
トレイトの廃止 - RFC 0469
box p
パターンをfeature gateにし、安定化するまで使用を禁止 - RFC 0490
Sized? T
記法をT: ?Sized
に変更
マクロ
- RFC 0085 パターンの位置でマクロを使えるようにする
- RFC 0378 文マクロには
{}
または;
を必須とし、どちらも持たないマクロは式マクロとして解釈する - RFC 0453 マクロのエクスポート (
#[macro_export]
/#[macro_reexport]
/#[macro_use]
)、$crate
メタ変数、プラグインのための#[plugin]
属性
モジュール・名前解決・可視性
- RFC 0001 構造体フィールドの可視性をデフォルトでprivateにする。
- RFC 0003 未使用の属性のチェック方法を改善する
- RFC 0026
enum
のバリアントの可視性を常にpublicにし、priv
キーワードを廃止する。 - RFC 0109 バージョン込みでcrateを指定できるcrate idを廃止して、ソースコードレベルではcrateの名前だけを指定するようにする
- RFC 0116
use
/let
等による同レベルシャドウイングの廃止 (現在はglob importの復活により、globのshadowingが可能) - RFC 0136 publicな定義の型にprivateな型を使うのを禁止する
- RFC 0155 固有実装は該当の型と同じモジュールでのみ可能 (RFC 0735により廃止)
- RFC 0234
enum
のバリアントは常に値名前空間と型名前空間の両方に属するようにする - RFC 0385 モジュールシステムの整理:
use
/mod
順の強制を廃止、pub extern crate
を許可、extern crate
のブロック内の出現を許可 - RFC 0390 バリアントを
enum
の名前空間の中に移動する。つまり、enum Foo { Bar }
でBar
ではなくFoo::Bar
とアクセスする - RFC 0459
impl
とその中のfn
の間での、生存期間変数と型変数のシャドウイングを禁止
型システム
- RFC 0019 既定実装
impl Send for .. {}
, 否定実装impl !Send for T {}
によりSend
/Sync
をライブラリレベルで実現する (#![feature(optin_builtin_traits)]
) - RFC 0034/0011
struct
/enum
の型引数に境界T: Trait
を書けるようにし、それが充足されていることを利用側で検査する。 - RFC 0048 トレイト周りの整理:
self
の一般化、coherence条件の整理、トレイト選択アルゴリズムの改善など - RFC 0111
Index
をIndex
/IndexMut
に分割する - RFC 0112/0033
Box<T>
から&mut T
への型強制の廃止(DerefMut型強制とは別) (RFC 0139も参照) - RFC 0115 整数リテラル型がデフォルトで
isize
にフォールバックしないようにする (RFC 0212により廃止) - RFC 0139
Box<T>
から&T
への型強制の廃止 (Deref型強制とは別) (RFC 0112も参照) - RFC 0195 関連型・関連定数・関連生存期間 (関連生存期間は未実装)
- RFC 0212 RFC 0115をリバートするが、整数型のデフォルトは
isize
ではなくi32
にする - RFC 0213
fn
/impl
でもデフォルト型引数を使えるようにする。_
を型引数のデフォルト値の意味で使えるようにする (#![feature(default_type_parameter_fallback)]
) - RFC 0241 Deref型強制:
&Rc<T>
を&T
に自動変換等 - RFC 0255 トレイトにobject-safetyを課すようにする
- RFC 0341 virtual構造体と継承
virtual struct Foo {}
struct Bar : Foo {}
の削除 - RFC 0401 型強制とキャストの整理: スライスへの自動参照の削除、生ポインタ型強制、サブトレイト型強制、unsizedタプル型強制、推移的型強制、ユーザー定義型のunsize型強制など (RFC 0982, RFC 1558も参照; いくつかの機能は未実装)
- RFC 0447 未使用だったり、一意でないような
impl
の型引数を禁止 - RFC 0495 スライスパターン
[x, xs..]
の変更:[T]
にはマッチするが&[T]
にはマッチしない。可変借用の分割に対応 (#![feature(slice_patterns)]
/#![feature(advanced_slice_patterns)]
に注意)
クロージャ
- RFC 0114 クロージャの整理: unboxed closureのための
Fn
/FnMut
/FnOnce
トレイトの導入、proc
の削除、クロージャ型を削除して|_| -> _
を構文糖衣に変更(構文糖衣はRFC 0231により廃止)、キャプチャー方式の指定、レシーバモード|:|
|&:|
|&mut :|
の明示(RFC 0231により廃止) - RFC 0151 クロージャで
ref
を指定しない限りムーブキャプチャーする (RFC 0231により廃止) - RFC 0231 キャプチャーモードの推論方式を変更、
ref ||
を廃止してmove ||
を導入、|_| -> _
型構文を廃止、レシーバモード|:|
|&:|
|&mut :|
を廃止
生存期間/ボローチェッカー
- RFC 0066 一時的な値に対する参照を間接的に取った場合も、直接取った場合と同様にそ の生存期間を延長できるようにする
- RFC 0107 パターンマッチのガード内でムーブ束縛された変数を使う (未実装)
- RFC 0130 ボローチェッカーにおける
Box<T>
の特別扱いの廃止 - RFC 0141 生存期間の省略規則の整理
- RFC 0192 トレイトオブジェクト型の生存期間 (RFC 0599, RFC 1156も参照)
- RFC 0246
static
をconst
/static
に分割 - RFC 0387 高階トレイト境界
T: for<'a> Trait
の導入
コード生成/ABI
- RFC 0008
extern "rust-intrinsic"
の廃止 (破棄) - RFC 0079
#[repr(C)]
などで明示しない限り、構造体レイアウトは入れ替えられる可能性がある - RFC 0320 構造体等からdrop flagフィールドを削除、変数ごとのdrop flagに移行
- RFC 0379 ランタイムリフレクションと、それを用いた
{:?}
の削除、libdebug
/Poly
の削除 ({:?}
はRFC 0504により復活)
ライブラリ
- RFC 0040
libstd
の実装をlibcore
,liballoc
,liblibc
などのライブラリに分割する。 - RFC 0042/0007
regex
crateの同梱 (現在は同梱されていない) - RFC 0050 デバッグモードでのみ有効化される
debug_assert!()
の導入 - RFC 0060
StrBuf
をString
にリネーム - RFC 0093
println!
やformat!
から地域化の機能を削除し、構文を整理 - RFC 0100
PartialOrd::partial_cmp
を追加 - RFC 0123
Share
をSync
にリネーム - RFC 0201
std::error::Error
トレイトによるエラーの相互変換 - RFC 0216
HashMap
等でfindの結果を保持するEntry
型の導入 - RFC 0221
fail!()
をpanic!()
にリネーム - RFC 0230 標準ライブラリからグリーンスレッド関係の部分を削除
- RFC 0235 コレクション関連ライブラリの整理と慣例の確立:
Cow
を導入、Deque
などの抽象化用トレイトを削除しIterator
を中心にした枠組みを整備、各種関数の命名規則を統一 - RFC 0256
Gc<T>
/@T
(Rc<T>
とは異なり、循環参照も解放される) の削除 - RFC 0356 型名にモジュール名を含めない慣習を定義し、
io::IoError
をio::Error
にリネーム - RFC 0369
std::num
にあった様々な抽象的な数値型トレイトの削除 (現在はSigned
も含め大部分が削除されnum
crateに移管済み) - RFC 0380
std::fmt
の安定化 - RFC 0439
std::cmp
とstd::ops
の整理: 演算子オーバーロードトレイトの整理、ヘテロジェニアスなEq
,Slice
/SliceMut
を削除してRange*
型を導入、IndexSet
の導入 (IndexSet
は実装されていない模様; RFC issue #997 も参照) - RFC 0458
Send: 'static
を削除、&T: Send
←→T: Sync
- RFC 0461 タスクローカル領域のための
std::local_data
を整理してスレッドローカル領域のためのstd::tls
を導入 (現在はstd::thread
に統合) - RFC 0474
std::path
の整理: 正規化の意味論を変更、UTF-8とは限らないパスのためにPathBuf
/OsPath
型を新たに導入 - RFC 0486
std::ascii::Ascii
型を削除してascii
外部crateに分離 - RFC 0494
std::c_vec
を廃止、std::c_str
をstd::ffi
にリネーム、CString
の所有権の扱いを変更
コンパイラ/リンカ/Cargo
RustのRFC一覧 (~0200)
概要: RustのRFCの一覧を作ろうとしたが、あまりに多いのでとりあえず0200までで公開することにした。なお、単に全てのRFCを列挙したいならばここを見ればよい。
このRFCはRustのコミュニティーが管理しているものであり、 “RFC” の元祖であるIETF RFCとは関係ない。いずれもrequest-for-commentの略である。
メタ
スタイル
構文
- RFC 0016 属性を
let
文、ブロック、式にも使えるようにする (#![feature(stmt_expr_attributes)]
) - RFC 0049 マッチ腕に属性を使えるようにする
- RFC 0059
~T
型と~x
式を削除し、Box
/box
で置き換える - RFC 0063 二重インクルードによる混乱を防ぐため、
mod foo;
による外部ファイルの読み込みに制限をつける - RFC 0068
*T
型を*const T
にリネーム - RFC 0069 バイトリテラル
b'x'
とバイト列リテラルb"Foo"
の導入 - RFC 0071
const
/static
内でも、一定条件下でブロックを使えるようにする - RFC 0085 パターンの位置でマクロを使えるようにする
- RFC 0087 トレイトオブジェクトの追加の境界を
&(Foo : Send + Sync)
ではなく&(Foo + Send + Sync)
のように指定するようにする - RFC 0090/0021 字句解析器をより単純にする
- RFC 0092
if
やfor
などのブロックと紛らわしい場面では構造体の{}
を禁止する - RFC 0132 UFCS (
<T as Trait>::sth
/<T>::sth
) の導入 - RFC 0135
where
節の導入 (一部未実装) - RFC 0160
if let
構文 - RFC 0164 スライスパターンをfeature gateにする
- RFC 0168
{mod, ..}
によりモジュール自体とその中身を同時にインポートする (RFC 0532により廃止) - RFC 0169
use baz = foo::bar;
構文を廃止しuse foo::bar as baz;
で置き換える - RFC 0179
&p
パターンを&p
と&mut p
パターンに分割し、参照のミュータビリティーに応じて使い分けるようにする - RFC 0184
foo.0
のように、タプル/タプル構造体のフィールドに整数でアクセスできるようにする - RFC 0194
#[cfg(...)]
の構文の整理 - RFC 0198
foo[]
/foo[n..m]
/foo[n..]
/foo[..n]
のためのトレイトSlice
/SliceMut
を導入 (RFC 0439により廃止:foo[]
はfoo[..]
になり、..
は[]
と独立な構文になった)
意味論・型システム
- RFC 0001 構造体フィールドの可視性をデフォルトでprivateにする。
- RFC 0003 未使用の属性のチェック方法を改善する
- RFC 0008
extern "rust-intrinsic"
の廃止 (破棄) - RFC 0019 既定実装
impl Send for .. {}
, 否定実装impl !Send for T {}
によりSend
/Sync
をライブラリレベルで実現する (#![feature(optin_builtin_traits)]
) - RFC 0026
enum
のバリアントの可視性を常にpublicにし、priv
キーワードを廃止する。 - RFC 0034/0011
struct
/enum
の型引数に境界T: Trait
を書けるようにし、それが充足されていることを利用側で検査する。 - RFC 0048 トレイト周りの整理:
self
の一般化、coherence条件の整理、トレイト選択アルゴリズムの改善など - RFC 0066 一時的な値に対する参照を間接的に取った場合も、直接取った場合と同様にその生存期間を延長できるようにする
- RFC 0079
#[repr(C)]
などで明示しない限り、構造体レイアウトは入れ替えられる可能性がある - RFC 0107 パターンマッチのガード内でムーブ束縛された変数を使う (未実装)
- RFC 0109 バージョン込みでcrateを指定できるcrate idを廃止して、ソースコードレベルではcrateの名前だけを指定するようにする
- RFC 0111
Index
をIndex
/IndexMut
に分割する - RFC 0112/0033
Box<T>
から&mut T
への型強制の廃止(DerefMut型強制とは別) (RFC 0139も参照) - RFC 0114 クロージャの整理: unboxed closureのための
Fn
/FnMut
/FnOnce
トレイトの導入、proc
の削除、クロージャ型を削除して|_| -> _
を構文糖衣に変更(構文糖衣は現在は廃止)、キャプチャー方式の指定、レシーバモードの明示(現在は廃止) - RFC 0115 整数リテラル型がデフォルトで
i32
にフォールバックしないようにする (RFC 0212により廃止) - RFC 0116
use
/let
等による同レベルシャドウイングの廃止 (現在はglob importの復活により、globのshadowingが可能) - RFC 0130 ボローチェッカーにおける
Box<T>
の特別扱いの廃止 - RFC 0136 publicな定義の型にprivateな型を使うのを禁止する
- RFC 0139
Box<T>
から&T
への型強制の廃止 (Deref型強制とは別) (RFC 0112も参照) - RFC 0141 生存期間の省略規則の整理
- RFC 0151 クロージャで
ref
を指定しない限りムーブキャプチャーする (RFC 0231により廃止) - RFC 0155 固有実装は該当の型と同じモジュールでのみ可能 (RFC 0735により廃止)
- RFC 0192 トレイトオブジェクト型の生存期間 (RFC 0599も参照)
- RFC 0195 関連型・関連定数・関連生存期間 (関連生存期間は未実装)
ライブラリ
- RFC 0040
libstd
の実装をlibcore
,liballoc
,liblibc
などのライブラリに分割する。 - RFC 0042/0007
regex
crateの同梱 (現在は同梱されていない) - RFC 0050 デバッグモードでのみ有効化される
debug_assert!()
の導入 - RFC 0060
StrBuf
をString
にリネーム - RFC 0093
println!
やformat!
から地域化の機能を削除し、構文を整理 - RFC 0100
PartialOrd::partial_cmp
を追加 - RFC 0123
Share
をSync
にリネーム
コンパイラ
Rustのトレイト選択
概要: トレイト選択とは、トレイト制約の解決方法を検索するコンパイラの処理であり、型推論にもコード生成にも使われる重要な部品である。本記事ではトレイト選択の動作の詳細について解説する。
トレイト選択とは
トレイト選択 (selection) とは、トレイト制約 (obligation) の解決方法を検索する処理である。トレイト制約は、トレイト境界と同じく、
SomeType: SomeTrait<SomeArguments>
という形式をとる。例えば、
let z = x + y;
というコードを書いたとき、 z
の型は <X as Add<Y>>::Output
であるが、これをより具体的に決定するには、 X: Add<Y>
の解決方法を決定する必要がある。
トレイト選択の処理はtraits/select.rs
にまとまっているのでここを読むとだいたいわかる。
トレイト選択が呼ばれるタイミング
トレイト選択の本体は select
関数 である。これは主に以下のような場所で呼ばれる。
- 型推論において型を1段階以上解決する必要が生じた場合。 (今あるトレイト制約それぞれについて解決を試みるが、失敗してもよい)
- 型推論の最後。 (全てのトレイト制約を解決する)
- 定数式の評価中。 (関連定数・
const
メソッド等の解決) - 単相化後のコード生成。
特に、型推論中のトレイト選択では、型引数を含む上、型の判明していない型推論変数もある。そのためこの時点ではトレイト選択の結果が曖昧である可能性がある。
トレイト制約の解決方法
トレイト制約はいくつかの異なる方法で解決される。それらの分類を与えているのがSelectionCandidate
である。
ImplCandidate
… 最も標準的な解決方法。トレイト実装 (impl MyTrait for SomeType { .. }
) により解決する。 (否定実装もこれに含まれる)ParamCandidate
… 最も標準的な解決方法。型引数等に対するトレイト境界 (fn f<T: Clone>
のT: Clone
など) により解決する。DefaultImplCandidate
… 既定実装impl Send for .. {}
により解決する。ProjectionCandidate
… 射影型のトレイト制約を、関連型に対するトレイト境界 (type Owned: Borrow<Self>;
など) により解決する。または、匿名型 (impl Trait
) のトレイト制約を、それ自身により解決する。ObjectCandidate
… トレイトオブジェクトのトレイト制約を、それ自身により解決する。- 特定のトレイトに対するビルトインの解決方法
BuiltinCandidate
…Copy
/Sized
制約を組込みの方法により解決する。BuiltinUnsizeCandidate
…Unsize
制約を組込みの方法により解決する。BuiltinObjectCandidate
… トレイトオブジェクトに追加で付与されるSend
/Sync
境界により解決する。ClosureCandidate
/FnPointerCandidate
… 関数ポインタ型fn()
や|args| { .. }
によるクロージャ匿名型[closure@..]
に対するFn
/FnMut
/FnOnce
を組込みの方法により解決する。
ImplCandidate
と ParamCandidate
は、この時点では全く別物であることに注意する。例えば X: Add<Y>
を解決する場合でも、
fn f(x: i32, y: i32) { let z = x + y; }
であれば ImplCandidate
が選択されるが、
fn f<X: Add<Y>, Y>(x: X, y: Y) { let z = x + y; }
であれば ParamCandidate
が選択される。
トレイト選択の流れ
トレイト選択は以下の手順で行われる。
基本的には、収集・選別をして候補が2つ以上になったら曖昧、候補が0個だったり承認されなかったら失敗、それ以外の場合は成功である。また、型環境への副作用は承認の段階で発生する。
実装候補の収集と承認
実装候補の収集
実装候補の収集は以下のように行われる。
- 該当トレイトに対する既知のトレイト実装を列挙する。
- 各トレイト実装の型引数/生存期間変数をフレッシュな型変数/生存期間変数で置き換える。
- トレイト実装のヘッダとトレイト制約の単一化を試みる。
- 単一化の成否にかかわらず、型環境を直前の状態にロールバックする。
- 単一化が成功した実装を、候補として残す。
例えば、 Vec<Box<i32>>: MyTrait<Vec<Box<i8>>>
というトレイト制約に対し、
impl<T: Copy, U: Copy> MyTrait<Vec<T>> for Vec<U> { .. }
は候補である。 (単一化に成功するため。実装に含まれるトレイト境界は満たされていないが、これは収集には関係ない。)impl<T> MyTrait<Vec<T>> for Vec<T> { .. }
は候補ではない。 (単一化に失敗するため)
実装候補の承認
実装候補の承認は以下のように行われる。
- 単一化を行う。
where
等で指定されたトレイト境界をトレイト制約として追加する。
型引数候補の収集と承認
型引数候補の収集
型引数候補の収集は以下のように行われる。
where
節等のトレイト境界を列挙する。- 各トレイト境界を、トレイト制約と単一化し、成功したら候補に加える。
- 単一化の成否にかかわらず、元の状態にロールバックする。
型引数候補の承認
型引数候補の承認は以下のように行われる。
- 該当トレイト境界を、トレイト制約と単一化する。
既定実装候補の収集と承認
既定実装候補の収集
既定実装は impl Send for .. {}
のように ..
という記号を使って与えられる特殊な実装で、主に Send
, Sync
, UnwindSafe
, RefUnwindSafe
が用いている。
既定実装候補の収集 は条件つきで実行される。つまり、他に候補がない場合にのみ既定実装候補がチェックされる。この規則があるため、収集と選別を区別して考える必要がある。
さて、他に候補がない場合の既定実装候補の収集は以下のように行われる。
- トレイトオブジェクト型には既定実装は使われない。 (必要なら
&(Foo + Sync)
のようにしたり、trait Foo : Sync
のようにしたりする) - 型引数には既定実装は使われない。 (必要なら
fn f<T: Send>
のように明示する) - 射影型には既定実装は使われない。 (必要なら
trait Foo { type X : Send; }
のように明示する) - それ以外の型に対しては既定実装候補を残す。特に、
impl Trait
型には既定実装が適用される。 - 型変数の場合は、上記のどちらの場合に落ちるかわからないため、曖昧となる。
既定実装候補の承認
既定実装候補の承認は以下のように行われる。なお、既定実装をもつことができるトレイトは、 Send
や Sync
のように引数をとらないマーカートレイトに限られる。
Self
型の各メンバーに、再帰的にトレイト制約を追加する。
ただし、各メンバーとは以下のことを指す:
- 整数・浮動小数点数・
bool
・char
・str
・関数ポインタ・関数定義・!
: なし - ポインタ・参照: 参照先の型
- 配列・スライス: 要素型
PhantomData<T>
:T
- タプル・構造体・共用体・列挙型: 全てのメンバーの型
- クロージャ: キャプチャーされた変数の型。ただし参照キャプチャーされた場合はその参照型。
impl Trait
: 匿名化される前の型。
射影候補の収集と承認
射影候補の収集
射影候補の収集は以下のようにして行われる。
Self
型が射影型なら、関連型の境界を列挙する。Self
型がimpl Trait
型なら、impl Trait
内のTrait
を列挙する。- これらのそれぞれと単一化し、どれか一つでも成功したら候補として採用する。
- 単一化の成否にかかわらず、型環境は元の状態にロールバックする。
現在の実装では、複数の候補があった場合、曖昧にはならず、最初のものが採用される。
射影候補の承認
射影候補の承認は以下のようにして行われる。
Self
型が射影型なら、関連型の境界を列挙する。Self
型がimpl Trait
型なら、impl Trait
内のTrait
を列挙する。- これらのそれぞれと単一化し、失敗したらロールバックする。成功したらロールバックせず終了する。
トレイトオブジェクト候補の収集と承認
トレイトオブジェクト候補の収集
トレイトオブジェクト候補の収集は以下のようにして行われる。
- トレイト制約のトレイトがobject-safeでないなら何もしない。
- トレイト制約の
Self
型がトレイトオブジェクト型でないなら何もしない。Self
型が型推論変数の場合は曖昧となる。 - トレイトオブジェクト型に追加の
Send
/Sync
境界があり、これがトレイト制約と一致する場合は、組込みトレイトオブジェクト候補として採用し、終了する。 - それ以外の場合は、トレイトオブジェクト型の主境界のスーパートレイト境界を(型引数込みで)推移的に求め、トレイト制約と単一化できるものを列挙する。(
trait Foo: Bar<i32> + Bar<i8>
のように、同じトレイトの複数のスーパートレイト境界を持つ可能性もある) - 残ったスーパートレイト境界がちょうど1個ならそれを候補にする。それ以外の場合は曖昧と報告する。
トレイトオブジェクト候補の承認
トレイトオブジェクト候補の承認は収集とほぼ同様だが、単一化に成功した場合はロールバックせずに型環境に対する変更をコミットする。
なお、組込みトレイトオブジェクト候補(追加の Send
/Sync
境界に由来する候補) の承認は何もしない。
クロージャ候補/関数ポインタ候補の収集と承認
クロージャ候補/関数ポインタ候補の収集
クロージャ候補の収集と関数ポインタ候補の収集は以下のようにして行われる。
- トレイト制約のトレイトが
Fn
/FnMut
/FnOnce
のいずれかを調べる。どれでもなかったら何もしない。 Self
がクロージャ型/関数定義/関数ポインタのいずれかを調べる。どれでもなかったら何もしない。未解決の型推論変数の場合は曖昧となる。- クロージャ型については、キャプチャー変数の利用状況を見て、
Fn
/FnMut
/FnOnce
が実装できるかどうかを調べる。実装できなかったら何もしない。この時点でクロージャ種別が判明していない場合は曖昧となる。 - 関数定義/関数ポインタについては、
unsafe
でないこと、extern "Rust"
である(これは何も書いていないのと同義)こと、可変長引数部分をもたないこと(extern "Rust"
であることからも従う)を確認する。どれか1つでも満たされていなかったら何もしない。 - それ以外の場合は、これを候補にする。
クロージャ候補/関数ポインタ候補の承認
クロージャ候補の承認と関数ポインタ候補の収集は以下のようにして行われる。
- クロージャ/関数ポインタ型から、それが実装するクロージャトレイト参照を生成する。 (例:
[closure@..]
からFn(u8) -> u32
/fn(&String) -> &str
からFnMut(&String) -> &str
) - 生成したクロージャトレイト参照を、トレイト制約と単一化する。
- クロージャの場合、クロージャの種別ごとに生じる制約を追加する。
Copy
/Sized
の収集と承認
Copy
はimpl
・関連型の境界・型引数の境界のほかに、以下の組込みの規則により候補が生成されることがある。
- 整数・浮動小数点数・
bool
・char
・関数ポインタ・関数定義・生ポインタ・共有参照・!
: 無条件でCopy
である。 - 配列: 要素型が
Copy
であるときにCopy
である。 - タプル: 全ての要素型が
Copy
であるときにCopy
である。 - トレイトオブジェクト・
str
・スライス・クロージャ型・可変参照:Copy
ではない。他の候補の有無にかかわらず、このトレイト制約は解決されない。 - 構造体・列挙型・共用体・射影型・型引数・
impl Trait
型:Copy
とは限らないが、impl Copy for
などの候補によりCopy
になるかもしれない。 - 未解決の型推論変数の場合: 曖昧となる。
Sized
にも同様の組込みの規則がある。
- 整数・浮動小数点数・
bool
・char
・関数ポインタ・関数定義・生ポインタ・参照・配列・クロージャ型・!
: 無条件でSized
である。 - タプル: 最後の要素型が
Sized
であるときにSized
である。 - トレイトオブジェクト・
str
・スライス:Sized
ではない。他の候補の有無にかかわらず、このトレイト制約は解決されない。 - 構造体・列挙型・共用体: 各バリアントの末尾の型が
Sized
であるとき、Sized
である。 - 射影型・型引数・
impl Trait
型:Sized
とは限らないが、T: Sized
などの候補によりSized
になるかもしれない。 - 未解決の型推論変数の場合: 曖昧となる。
これらの条件が満たされたときに、 Copy
/Sized
の組込みトレイト候補が生成される。候補の生成時点では、再帰的な制約は加味しない。
Copy
/Sized
の組込みトレイト候補の承認のタイミングで、これらの再帰的な制約がチェックされる。
Unsize
の収集と承認
T: Unsize<U>
は関連型の境界・型引数の境界のほかに、以下の規則により候補が生成される。
T
とU
がどちらも同じトレイトのトレイトオブジェクト型で、追加のSend
/Sync
境界についてT
のほうがU
より多くを実装しているときは、T: Unsize<U>
の候補が生成される。T
がトレイトオブジェクト型ではなく、U
がトレイトオブジェクト型のときは、T: Unsize<U>
の候補が生成される。U
がトレイトオブジェクト型ではなく、T
かU
が未解決の型推論変数のときは、曖昧となる。T
が配列でU
がスライスのときは、T: Unsize<U>
の候補が生成される。T
とU
がどちらも同じ構造体を指しているときは、T: Unsize<U>
の候補が生成される。T
とU
が同じ長さのタプルのときは、T: Unsize<U>
の候補が生成される。
これらの候補は以下の規則により承認される。
T
とU
の主境界は等しい。T
はU
より長く生存する。U
のトレイトはobject-safeである。T
はU
の全てのトレイトを実装している。T
はU
の生存期間以上に生存する。T
とU
の要素型が等しい。- 構造体はフィールドを1個以上持っている。最後以外のフィールド達と、最後のフィールドは、型引数を共有していない。最後以外のフィールドは
T
とU
で等しい。最後のフィールドをT0
,U0
とおくと、T0: Unsize<U0>
である。 (これらの比較時には射影型は解決せずにそのまま扱う) - タプルは1要素以上である。最後以外のフォールドは
T
とU
で等しい。最後のフィールドをT0
,U0
とおくと、T0: Unsize<U0>
である。
候補の選別
選別はさらに以下の処理に分けられる。
- 候補がちょうど1個なら、選別処理をスキップする。
- 各候補の承認処理をして、失敗したら候補から外す。承認の成否にかかわらず、型環境は元の状態にロールバックする。
- それでも候補が複数残っている場合は、各候補の特殊化関係を調べ、特殊化関係にあれば負けたほうを外す。
特殊化関係はcandidate_should_be_dropped_in_favor_of
にある。これは以下の規則になっている。
- 型引数候補は最も強い。異なる型引数候補同士は比べられない。
- 射影候補(
impl Trait
を含む)とトレイトオブジェクト候補(組込みトレイトオブジェクト候補を含まない)は、その次に強い。 - それ以外の候補は上記よりも弱く、お互いには比べられない。ただし、以下の例外がある。
- 2つの実装候補について、実装が特殊化関係にある場合は、より特殊なほうがより強い。
- 2つの実装候補について、
#![feature(overlapping_marker_traits)]
で、両方の実装がアイテムを持たず、極性も等しい場合には、同じ強さである。
同じ強さである場合も含めて、より弱いものから順番に脱落させていく。これにより最強の候補が1つだけ残ったら選別は成功となる。
トレイト選択では未解決の型推論変数が含まれていることがあるため、コヒーレンスに関係なく、複数の候補が残る場合があり、その場合はエラーになる。逆に、トレイト選択では基本的に現在見えている実装から探索するので、「これしか該当する実装がないから」という理由により型推論が進むことがある。以下はその例である。
use std::ops::Add; fn f<T: Add<i32>>(t: T) { t + Default::default(); // OK } fn g<T: Add<i32> + Add<i8>>(t: T) { t + Default::default(); // Error } fn main() { }
より奇妙な例としては、以下のようなものも考えられる。
fn f<T>() { let x : (_, T); // Error } fn g<T: ?Sized>() where (i32, T): Sized { let x : (_, T); // OK } fn main() { }
収集時には曖昧でも、選別によって曖昧性がなくなる例としては、例えば以下のようなものが考えられる。
trait Foo<Y> { fn y() -> Y; } trait Bar {} trait Baz {} impl<T: Bar> Foo<i32> for T { fn y() -> i32 { 0 } } impl<T: Baz> Foo<i8> for T { fn y() -> i8 { 0 } } impl Bar for i32 {} impl Baz for i64 {} fn f() { let _ = <i32 as Foo<_>>::y(); } fn main() { }
否定実装の処理
既定実装と異なり、否定実装は収集段階では通常の実装と同様に扱われている。否定実装の特徴は、この実装が選別された場合にはコンパイルエラーとなるという点である。
自己再帰的な制約の処理
自己再帰的なデータ構造の Send
の処理などで、自己再帰的な制約が出てくることがある。これは evaluate_stack
の一部分で処理されている。以下のように実装されている。
まとめ
トレイト選択の詳細について説明した。ただし、より一般の制約のfulfillmentや、coherence checkerなどについては言及していない。
Rustにおける左辺値選好と可変性調停
概要: Rustのミュータビリティー推論に使われる左辺値選好と可変性調停について説明する。
左辺値選好
Rustの型推論では、期待型のほかに、左辺値選好 (lvalue preference) という状態もトップダウンに渡される。
LvaluePreference
は以下のどちらかの値をとる。
PreferMutLvalue
… 可変な左辺値を優先する。NoPreference
… 選好はない。不変な左辺値や、右辺値などでよい。
左辺値選好はほぼ構文的に決定される。具体的には以下のように決定・伝搬される。
&mut
の内側の式はPreferMutLvalue
- 代入の左辺は
PreferMutLvalue
- メソッド呼び出しとフィールド参照のレシーバーは左辺値選好を継承する。
x[i]
の左辺は左辺値選好を継承する。- 参照外し演算子は左辺値選好を継承する。
左辺値選好は次の用途に用いられる。
x[i]
は原則としてIndex
に脱糖されるが、PreferMutLvalue
ならば先にIndexMut
を試す。*x
は原則としてDeref
に脱糖されるが、PreferMutLvalue
ならば先にDerefMut
を試す。- メソッド以外の自動参照外しに対しても同じ処理が行われる。
- メソッドのレシーバの自動参照外しは
NoPreference
が仮定される。 (後述)
左辺値選好が誤推論するケース
さて、この左辺値選好は多くの場合に正しく可変性を推論するが、誤った推論をするケースも考えられる。以下がこの左辺値選好の仮定である。
x[i]
の左辺は可変性を継承する→正しい。 (Index
/IndexMut
の型がそうなっているので)*x
は可変性を継承する→正しい。 (Deref
/DerefMut
の型がそうなっている。また、&T
/&mut T
/Box<T>
の参照外しの動作も同様である。)- フィールド参照は可変性を継承する→正しい。
- メソッド呼び出しは可変性を継承する→正しくない。
&mut self
を受け取り&T
を返したり、&self
を受け取り&mut T
を返したりする可能性がある。
これにより、メソッド呼び出しのレシーバの位置に Deref
や Index
が来ると、可変性の推論で誤推論を起こす可能性がある。これを実験すると以下のようになる。
use std::fmt::Debug; use std::ops::{Deref, DerefMut}; // deref/deref_mut 時にメッセージを出すラッパー #[derive(Copy, Clone, Debug)] pub struct Wrap<X: Debug + ?Sized>(X); impl<X: Debug + ?Sized> Deref for Wrap<X> { type Target = X; fn deref(&self) -> &Self::Target { println!("deref({:?})", self); &self.0 } } impl<X: Debug + ?Sized> DerefMut for Wrap<X> { fn deref_mut(&mut self) -> &mut Self::Target { println!("deref_mut({:?})", self); &mut self.0 } } // 左辺値選好による推論が失敗する例 #[derive(Copy, Clone, Debug)] pub struct A; impl A { pub fn f<'a, 'b>(&'a self, p: &'b mut i32) -> &'b mut i32 { p } pub fn g<'a, 'b>(&'a mut self, p: &'b i32) -> &'b i32 { p } } fn main() { let mut x = Wrap(A); let mut y : i32 = 0; // レシーバが&Tでかまわないが、構造上&mut Tが推論される例 &mut *(*x).f(&mut y); // deref_mut(Wrap(A)); &mut *x.f(&mut y); // deref(Wrap(A)); // レシーバが&mut Tを必要とするが、構造上&Tが推論される例 &*x.g(&y); // deref_mut(Wrap(A)); }
これを見ると、一番最初の例だけ、 Deref::deref
でよいはずの位置で DerefMut::deref_mut
が呼ばれているが、他の例では問題なく動作している。
これは次のような理由による。
- この例には書いていないが、
&T
で十分だが&mut T
が推論され、しかし実際にはDerefMut
が実装されていない場合には、Deref
にフォールバックされるため問題ない。 - メソッドのレシーバの自動参照外しでははじめ
DerefMut
ではなくDeref
が仮定される。 - メソッド解決後に発生する可変性調停のため、推論と異なり
&mut T
が必要な場合は、そのように修正される。
可変性調停
可変性調停 (reconciliation) はメソッドの解決後に行われる処理で、レシーバが &mut self
だった場合に Deref
/Index
を再解決する処理である。
これは次のような手順を踏む。
- レシーバを指定している式を調べ、影響を受ける
Deref
/Index
呼び出しを列挙する。 (例えばx[i][j].some_method()
でsome_method
のレシーバが&mut self
の場合、x[i]
とx[i][j]
を補正する必要がある。 - それぞれの
Deref
/Index
をPreferMutLvalue
で再推論する。これによりこの部分がDerefMut
/IndexMut
に昇格する。
これにより、&mut T
が要求される部分で &T
を誤推論したことにより発生しえたエラーを回避している。
まとめ
*x
やx[i]
などではイミュータブルなDeref
/Index
とミュータブルなDerefMut
/IndexMut
のいずれかが選択される。- これらは色々な仕組みによりだいたいいい感じに推論される。それには以下のような動作が関わってくる。
- 構文的に決まる「左辺値選好」をトップダウンに伝搬する。
DerefMut
が駄目ならDeref
にフォールバックする。- メソッド解決でははじめイミュータブルを仮定し、
&mut self
が要求されていたらミュータブルとして再推論する。
Rustのバイト列リテラルの型
Rustのリテラルの型検査は check_lit
にある。これによると、文字列リテラルの型は &'static str
である一方、バイト列リテラルの型は &'static [u8]
ではなく、 &'static [u8; N]
である。
そのため、以下のようなコードがコンパイルできる。
fn main() { let x : &[u8; 5] = b"Hello"; let y : [u8; 5] = *b"Hello"; }
なお、 b"Hello"
は、 &[72, 101, 108, 108, 111]
と等価ではない。数値リテラルが u8
とは限らないという点のほかに、生存期間が異なる。
fn main() { let x : &'static [u8] = b"Hello"; let y : &'static [u8] = &[72, 101, 108, 108, 111]; }
rustc 1.20.0-nightly (3bfc18a96 2017-06-29) error[E0597]: borrowed value does not live long enough --> <anon>:3:30 | 3 | let y : &'static [u8] = &[72, 101, 108, 108, 111]; | ^^^^^^^^^^^^^^^^^^^^^^^^ does not live long enough 4 | } | - temporary value only lives until here | = note: borrowed value must be valid for the static lifetime... error: aborting due to previous error(s)
生存期間を考慮すると、 b"Hello"
はおよそ以下のように脱糖されると考えられる。
fn main() { let x : &'static [u8] = b"Hello"; let y : &'static [u8] = { static S : [u8; 5] = [72, 101, 108, 108, 111]; &S }; }
Rust unsize期待型とキャスト期待型
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節側で厳しすぎる推論をしてしまうことを防止するためである。