25
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rustの構造体に継承のようなものを適用する

Posted at

疑問

rustcのソースコードを読んでいたら、初見殺しテクニカルだと思う書き方に遭遇しました。例えばこんな感じ。

compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
...
    pub fn local_ty(&self, span: Span, nid: hir::HirId) -> LocalTy<'tcx> {
        self.locals.borrow().get(&nid).cloned().unwrap_or_else(|| {
            span_bug!(span, "no type for local variable {}", self.tcx.hir().node_to_string(nid))
        })
    }
...
}

selfのメンバであるlocalsにアクセスして、そこからメソッドをつないでいっている普通の書き方に見えますね。でも、ここでselfの型となっている構造体FnCtxtにはlocalsなんてメンバは存在しないのです。

周辺のソースを探っていると、このlocalsInheritedという構造体のメンバのようでした。

compiler/rustc_typeck/src/check/inherited.rs
pub struct Inherited<'a, 'tcx> {
...
    pub(super) locals: RefCell<HirIdMap<super::LocalTy<'tcx>>>,
...
}

それを踏まえて構造体FnCtxtのメンバを見てみると、FnCtxtInheritedの参照をメンバとして持っていることが分かります。

compiler/rustc_typeck/src/check/fn_ctxt/mod.rs
pub struct FnCtxt<'a, 'tcx> {
...
    pub(super) inh: &'a Inherited<'a, 'tcx>,
}

とはいえ、普通に考えたらFnCtxtからInheritedのメンバにアクセスするには、self.inh.localsのようにinhをかませる必要がある気がします。どうなっているのでしょうか?

回答

実は、FnCtxtに実装されているトレイトDerefに秘密があります。

compiler/rustc_typeck/src/check/fn_ctxt/mod.rs
impl<'a, 'tcx> Deref for FnCtxt<'a, 'tcx> {
    type Target = Inherited<'a, 'tcx>;
    fn deref(&self) -> &Self::Target {
        &self.inh
    }
}

FnCtxtの参照を外すと、inhが返されるように実装されていました。この実装によりinhの記述は省略され、FnCtxtから直接Inheritedのメンバにアクセスできるようになっています。

これは見方を変えれば、FnCtxtInherited継承していると言うこともできるでしょう。(型の名前からして、そのような実装を意図していそうな感じです。)このやり方を使うことで、データ構造の継承機能を持たないRustに、構造体の継承のようなものを適用することができます。

注意

ただし、上記のような実装は推奨されていないようです。実際、DerefのリファレンスにもDerefはスマートポインタに対してのみ実装するべきとの記述があります。rustcで使っておいてひどい。

Because of this, Deref should only be implemented for smart pointers to avoid confusion.

ここでは、Derefはもともとスマートポインタ用に設計されたので、という文脈ではありますが、そうでなくとも今回紹介した書き方は読みづらいかなと思います。

余談

ここまでの説明を読んで、「Derefinhを返すことは分かったけど、参照外しなんてしてなくない?」という疑問を持った方がいるかもしれません。

これにはRustの型強制(Type Coercion)の仕組みが効いています。Rustでは、ある型のメンバやメソッドにアクセスする際、指定された型そのものに実装がない場合には参照を外した型でそれらが見つかるかを調べます。

つまり、冒頭の例は以下のような流れでメンバを探していくことになります。

self.locals... // selfにlocalsは見つからない
(*self).locals... // 参照を外して見つかるか調べる
(self.deref()).locals... // 参照外しはderefに置き換えられる
(self.inh).locals... // inhの中にlocalsが見つかる

違和感を感じるかもしれませんが、このような動作はおそらく普段も目にしていると思います。例えば、絶対値を求める単純なコードを考えてみます。

let a = -3_i32;
let b = &a;
assert_eq!(b.abs(), 3);

bの型は明らかに&i32です。にもかかわらずi32のメソッドであるabs()を呼び出すことができているのは、自動的に参照を外してメソッドを探しているためで、結果として以下のコードと同じことになります。

let a = -3_i32;
let b = &a;
assert_eq!((*b).abs(), 3);

このような型強制はRustを書きやすくしている一方で、深く考え始めると少しややこしいかもしれませんね。冒頭の例はこの機能をひねった使い方と言えそうです。

25
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?