Rustのself引数まとめ
概要: 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
フラグが立ち、構文のみならず型システムにも影響を与える。