概要: Rustの随所でself引数は特別扱いされている。それらの挙動について調べた。
self引数とメソッド
Rustではnon-staticメソッドは self という特殊な名前の引数を持つ関数として定義されている。例えば、
struct A; // parse_self_arg impl A { fn f1(self: A) {} fn f2(self: &mut A) {} fn f3(self: &A) {} fn f4(self: Box<A>) {} // 生存期間を明示すると以下の通り // fn f2<'a>(self: &'a mut A) {} // fn f3<'a>(self: &'a A) {} }
と書くと、 f1, f2, f3 はメソッドになる。
self はキーワードであり、この名前の引数は特定の条件下でのみ宣言できる。それは以下の場合である。
- traitまたはimpl内の関数の引数である。
- 第一引数である。
Self,&Self,&mut Self,Box<Self>のいずれかの型をもつ。(引数自体はmutであってもなくてもよい)
selfショートカット構文
self 引数は頻出するため、以下の構文糖衣が用意されている。
struct A; // parse_self_arg impl A { fn f1(self) {} fn f1mut(mut self) {} fn f2(&mut self) {} fn f3(&self) {} // 生存期間を明示すると以下の通り // fn f2<'a>(&'a mut self) {} // fn f3<'a>(&'a self) {} // 以下と同じ // fn f1(self: Self) {} // fn f1mut(mut self: Self) {} // fn f2(self: &mut Self) {} // fn f3(self: &Self) {} // fn f2<'a>(self: &'a mut Self) {} // fn f3<'a>(self: &'a Self) {} }
生存期間の省略
関数宣言で生存期間の指定を省略した場合、一定の規則に基づいて生存期間が復元される。このときに self 変数が特別扱いされる。具体的には、以下の規則に基づいている。
- 入力側で生存期間が省略された場合、出現位置ごとに別々のfreshな生存期間が割り当てられる。
- 出力側で生存期間が省略された場合、以下の規則に基づき、全て同じ生存期間が割り当てられる。
- もし、参照型の
self引数がある場合、その参照の生存期間が用いられる。 - もし、入力側に生存期間が1つだけ出現する場合、その生存期間が用いられる。
- それ以外の場合、コンパイルエラー。
- もし、参照型の
メソッド記法
レシーバーにドットをつける receiver.method(args) という記法は、 self 引数を持つメソッドにのみ有効である。
object safety
trait objectを生成できるtraitには条件がある。これをobject safetyというのであった。
あるtraitがobject safeであるとは、
Self: Sized制約がない、かつ- 束縛/where/スーパートレイトの制約におけるトレイトの型引数に
Selfが出現しない、かつ - 全てのメソッドがobject safeである。
ただし、あるメソッドがobject safeであるとは、そのメソッドに Self: Sized 制約がついているか、以下が満たされていることである。
self引数を持ち、かつself引数以外の引数・戻り値型にSelfを含まず、かつ- メソッドが型引数をとらない。
ObsoleteVisiblePrivateTypesVisitor
後方互換性のために残されているprivateness checkerで、 self 引数が特別扱いされている。詳細は不明
self 引数を回避する利点
ほとんどの場合、上記の条件を満たす引数は self にしてしまうほうが便利である。しかし std::rc::Rc と std::sync::Arc は self を使わない。
impl<T: ?Sized> Rc<T> { pub fn downgrade(this: &Self) -> Weak<T> { ... } ... }
この場合、同じ型をもつ関数でも、 self 引数のもつ利点は受けられない。 Rc や Arc が self を使わないのは、これが Deref を実装するコンテナであり、 Rc<T> のメソッド記法が T のメソッド記法の名前空間を汚染しないようにしたいからである。
コンパイラの該当箇所
syntax::parse::parser::Parser::parse_self_arg… self引数およびselfショートカットの構文解析syntax::ast::SelfKind… self引数の抽象構文。値渡しのショートカット/参照渡しのショートカット/型を明示する記法syntax::ast::FnDecl::is_self… ASTにおいて、関数がself引数を持つかrustc::hir::AssociatedItemKind::Method…is_selfを保持している。rustc::hir::lowering::LoweringContext::lower_trait_item_ref,rustc::hir::lowering::LoweringContext::lower_impl_item_ref… traitとimplそれぞれについて、ASTのhas_selfをHIRのhas_selfに保存している。rustc::ty::AssociatedItem…method_has_self_argumentを保持している。rustc_typeck::check::compare_method… trait implの関数がtraitの関数と一致しているか調べるときに、method_has_self_argumentの一致もチェックしている。rustc_typeck::check::wfcheck::CheckTypeWellFormedVisitor::check_method_receiver…self引数があるとき、その型はSelf,&mut Self,&Self,Box<Self>のいずれかであることをチェックしている。rustc_typeck::check::method::probe::ProbeContext::has_applicable_self… あるアイテムがメソッド記法の探索対象かどうかを調べている。rustc::ty::TyCtxt::virtual_call_violation_for_method…Sizedでないメソッドのobject safetyをチェックしている。
まとめ
Rustではメソッドの第一引数に self という特別な名前をつけることができる。これによりメソッドに has_self フラグが立ち、構文のみならず型システムにも影響を与える。