やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustのスマートポインタ(Derefトレイト)

 参照外し*の振る舞いをカスタマイズできる。

成果物

参考

参照外し

main.rs

fn main() {
    let x = 5;
    let y = &x;
    assert_eq!(5, x);
    assert_eq!(5, *y);
    assert_eq!(5, y);
}

 変数yは変数xの参照である。その値を参照するために参照外し演算子*を付与する。

 もし*を外してassert_eq!(5, y);にしたら以下エラーになる。

$ rustc main.rs
error[E0277]: can't compare `{integer}` with `&{integer}`
  --> main.rs:10:5
   |
10 |     assert_eq!(5, y);
   |     ^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
   |
   = help: the trait `std::cmp::PartialEq<&{integer}>` is not implemented for `{integer}`
   = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

Boxを参照のように使う

fn main() {
    let x = 5;
    let y = Box::new(x);
    assert_eq!(5, x);
    assert_eq!(5, *y);
}

 *を付与したときも、しないときも、参照のときと同じ挙動。

独自のスマートポインタを定義する

struct MyBox<T>(T);
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}
fn main() {
    let x = 5;
    let y = MyBox::new(x);
    assert_eq!(5, x);
    assert_eq!(5, *y);
//    assert_eq!(5, y);
}

 コンパイルエラー。「参照外しできない」と怒られる。

$ rustc main.rs
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
  --> main.rs:15:19
   |
15 |     assert_eq!(5, *y);
   |                   ^^

Derefトレイトを実装して参照外しする

use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}
impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &T { &self.0 }
}
fn main() {
    let x = 5;
    let y = MyBox::new(x);
    assert_eq!(5, x);
    assert_eq!(5, *y);
//    assert_eq!(5, y);
}
  • 関連型: type Target = T;
  • 参照する値を返す: &self.0

暗黙的な参照外し型強制

 とは何か?

参照外し型強制は、 Derefを実装する型への参照をDerefが元の型を変換できる型への参照に変換します。

 いつ発生する?

特定の型の値への参照を関数やメソッド定義の引数型と一致しない引数として関数やメソッドに渡すときに自動的に発生します。

 たとえば先述までのコードを以下のように変える。

fn hello(name: &str) {
    println!("Hello, {}!", name);
}
fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}

 関数helloの引数は&str型。なのに渡されたのはMyBox<String>。でも動く。「参照外し型強制」のおかげで。

 疑問。String型はstrに変換できるから「参照外し型強制」の対象になるってこと? 他に変換できる型は?

 もし参照外し型強制がなければ、以下のようなコードを書かねばならなかった。

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&(*m)[..]);
}

 疑問。上記コードで渡された型は&&strになってhello関数の引数&strに一致しないのでは?

  1. (*m)で参照外しderef()を実行して参照する値Stringを返す
  2. String[..]でスライス(&str化)する
  3. &strの参照&&strhelloに渡す

可変性

  • T: Deref<Target=U>の時、&Tから&U
  • T: DerefMut<Target=U>の時、&mut Tから&mut U
  • T: Deref<Target=U>の時、&mut Tから&U

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで