読者です 読者をやめる 読者になる 読者になる

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な生存期間が割り当てられる。
  • 出力側で生存期間が省略された場合、以下の規則に基づき、全て同じ生存期間が割り当てられる。
    1. もし、参照型の self 引数がある場合、その参照の生存期間が用いられる。
    2. もし、入力側に生存期間が1つだけ出現する場合、その生存期間が用いられる。
    3. それ以外の場合、コンパイルエラー。

メソッド記法

レシーバーにドットをつける 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::Rcstd::sync::Arcself を使わない。

impl<T: ?Sized> Rc<T> {
    pub fn downgrade(this: &Self) -> Weak<T> {
        ...
    }
    ...
}

この場合、同じ型をもつ関数でも、 self 引数のもつ利点は受けられない。 RcArcself を使わないのは、これが Deref を実装するコンテナであり、 Rc<T> のメソッド記法が T のメソッド記法の名前空間を汚染しないようにしたいからである。

コンパイラの該当箇所

まとめ

Rustではメソッドの第一引数に self という特別な名前をつけることができる。これによりメソッドに has_self フラグが立ち、構文のみならず型システムにも影響を与える。