1最小の Deref 実装 — * の意味を自分で決める
自作の MyBox に Deref を実装し、* で中身の値に届くようにする最小の例です。*m と書いた瞬間、コンパイラがそれを deref() の呼び出しに置き換えています。type Target は「* で取り出される型の宣言」という決まり文句で、この2点が Deref 実装の全部です。
use std::ops::Deref;
struct MyBox<T> {
value: T,
}
impl<T> Deref for MyBox<T> {
type Target = T; // * で取り出される型
fn deref(&self) -> &T {
&self.value
}
}
fn main() {
let m = MyBox { value: 10 };
println!("*m = {}", *m); // * が deref() の呼び出しに置き換わる
println!("*m * 3 = {}", *m * 3);
}
2参照外し型強制 — &Box<String> がそのまま &str になる
&str を取る関数に &String も &Box<String> もそのまま渡せることを確かめる例です。Deref の実装をたどって &Box<String> → &String → &str と何段でも自動変換されるのが「参照外し型強制」です。普段なにげなく String を &str の引数に渡せていた理由が、まさにこの仕組みです。
fn shout(text: &str) {
println!("{}!", text.to_uppercase());
}
fn main() {
let owned = String::from("hello");
shout(&owned); // &String → &str に自動変換
let boxed = Box::new(String::from("heap hello"));
shout(&boxed); // &Box<String> → &String → &str と多段で変換される
}
3メソッド呼び出しも deref を通る
Deref を実装した型では、* を書かなくても中身のメソッドをドット記法で直接呼べることを示す例です。w.len() と書くと、コンパイラが「Wrapper には len がない → deref して String で探す」という探索を自動でやってくれます。Box の中身のメソッドがそのまま呼べていたのも同じ仕組みです。
use std::ops::Deref;
struct Wrapper {
inner: String,
}
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &String {
&self.inner
}
}
fn main() {
let w = Wrapper { inner: String::from("rust") };
println!("len = {}", w.len()); // String のメソッドがそのまま呼べる
println!("upper = {}", w.to_uppercase());
}
4newtype パターン — 専用の型に str の能力を持たせる
メールアドレス専用の型 Email を作りつつ、Deref で Target = str にして文字列の読み取りメソッドをそのまま使えるようにする実務寄りの例です。「ただの String と区別できる型にしたいが、読み取り操作は委譲したい」という newtype パターンの定番構成です。ライブラリのラッパー型が中身のメソッドを使えるのも、多くはこの作りです。
use std::ops::Deref;
struct Email {
address: String,
}
impl Deref for Email {
type Target = str; // 読み取りは str に委譲する
fn deref(&self) -> &str {
&self.address
}
}
fn main() {
let email = Email { address: String::from("alice@example.com") };
println!("形式OK? {}", email.contains('@')); // str のメソッドを直接呼べる
println!("文字数 = {}", email.len());
}
5deref の呼び出しを観測する — * は隠れたメソッド呼び出し
deref() の中に println! を仕込み、* を書くたびに本当に deref が呼ばれていることを出力で観測する例です。「* は演算子ではなく deref() というメソッドの呼び出し」という抽象的な説明が、目に見える形で確認できます。デバッグ用に値へのアクセスを観測したいときにも応用できる作りです。
use std::ops::Deref;
struct Traced {
value: i32,
}
impl Deref for Traced {
type Target = i32;
fn deref(&self) -> &i32 {
println!("(deref が呼ばれました)");
&self.value
}
}
fn main() {
let t = Traced { value: 7 };
let sum = *t + *t; // * を2回書いたので deref も2回呼ばれる
println!("sum = {}", sum);
}