スマートポインタ (DerefDerefMutトレイトを実装する型) まわりの慣習について。

概要

スマートポインタでは、メソッドとして実装できる処理であっても、通常の関連関数として定義されている事が多い (言い換えると、スマートポインタでは self&self などの引数の使用が少ない)。

理由

これにはコードを分かりやすくする狙いがある。

ここで、ドット演算子からのメソッドの呼出において、スマートポインタには Deref 型強制がある。つまり、指定されたメソッド名は、スマートポインタ自身のメソッド、そして参照先のメソッド、その両方から探される。

そのため、コードリーディング時はそのメソッドがどちらに属するのか、そのつど調べないといけない。また、コードライティング時はメソッド名が衝突している場合、参照先のメソッドを呼ぶには、まず手動で参照を外さないといけない。一方、スマートポインタ側の処理の多くがそもそもメソッドでなければ、この問題はいくらか緩和される。

基準

関数をメソッドにすべきかどうかは、その処理対象による。具体的には、処理対象がスマートポインタ自身ならば通常の関連関数、処理対象がスマートポインタの参照先ならばメソッドとする。そのため、Box<dyn Any>downcast<T>(self) 等はメソッド形式である。

限界

この方法には限界もあり、スマートポインタが実装するトレイトのメソッドまでは呼出方法を強制できない。これには、Box<T, A> に実装される Clone::clone(&self) 等がある。Box::clone(x) 等と関数形式で呼ぶ事もできるが、あくまで任意であり一般的でもない。

実例

std クレートの多くのスマートポインタも、この慣習に従っている。

なお、Rc<T, A>::downgrade(this: &Rc<T, A>) の逆操作 Weak<T, A>::upgrade(&self) はメソッド形式である。この違いは RcWeakDeref の実装有無に由来する。

サンプル

以下では、MyPtr 型の dup 関数は _dup 関数より好ましい実装方法である。


use std::ops::Deref;

fn main() {
    let p1 = MyPtr::new(42);
    let p2 = MyPtr::dup(&p1);
    assert_eq!(*p1, *p2);
}

struct MyPtr<T> {
    value: Box<T>
}

impl<T: Clone> MyPtr<T> {
    fn new(value: T) -> Self {
        Self { value: Box::new(value) }
    }

    fn dup(this: &Self) -> Self {
        Self { value: this.value.clone() }
    }

    fn _dup(&self) -> Self {
        Self { value: self.value.clone() }
    }
}

impl<T> Deref for MyPtr<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value.as_ref()
    }
}