Debug traitを実装したかった
RustのThe bookをとりあえず一周して、読みかけだったプログラミングRustを見返しているところで、サンプルコードを自分なりにいじってみました。やりたかったことは単なるDebug
トレイトの実装なのですが・・・。
動機
プログラミングRust, p.91-92にあったサンプルコードを見て、ふと関数show
をDebugトレイト実装してやればいいんじゃないかと思ったのがキッカケです。
サンプルコード
そのまま引用します。
use std::collections::HashMap;
type Table = HashMap<String, Vec<String>>;
fn show(table: Table) {
for (artist, works) in table {
println!("works by {}:", artist);
for work in works {
println!(" {}", work);
}
}
}
fn main() {
let mut table = Table::new();
table.insert(
"Gesualdo".to_string(),
vec![
"many madrigals".to_string(),
"Tenebrae Responsoria".to_string(),
],
);
table.insert(
"Caravaggio".to_string(),
vec![
"The musicians".to_string(),
"The Calling of St. Matthew".to_string(),
],
);
table.insert(
"Cellini".to_string(),
vec![
"Perseus with the head of Madusa".to_string(),
"a salt cellar".to_string(),
],
);
show(table);
}
HashMap<String, Vec<String>>
に対してTable
というアイリアスを設定していますね。これに対してshow
というTable
の中身をコンソールに出力する関数を作っていますこの実行結果は次のようになります。
$ cargo run
...
works by Cellini:
Perseus with the head of Madusa
a salt cellar
works by Caravaggio:
The musicians
The Calling of St. Matthew
works by Gesualdo:
many madrigals
Tenebrae Responsoria
確かにTable
の中身が表示されていますね。
show関数いる・・・?
そう言えばRustにはDebug
トレイトがあるので、それを実装してやれば・・・という気の迷いを起こしたのがすべてのことの始まりです。まぁ、Table
がHashMap<String, Vec<String>>
へのエイリアスな時点で筋が悪そうなんですが、コンパイラのご指導のもと四苦八苦してみたのでよければお付き合いください。目標は次のコードで中身をコンソールに出力することです。
- show(table);
+ println!("{:?}", table);
Debugトレイトを実装
それでは早速Debug traitトレイトを実装してみます。
use std::fmt;
impl fmt::Debug for TableWrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (artist, works) in self {
writeln!(f, "works by {}:", artist)?;
for work in works {
writeln!(f, " {}", work)?;
}
}
Ok(())
}
}
これをもとのコードに追加してコンパイルすると・・・。
$ cargo build
...
rror[E0119]: conflicting implementations of trait `std::fmt::Debug` for type `std::collections::HashMap<std::string::Str
ing, std::vec::Vec<std::string::String>>`:
--> src/main.rs:7:1
|
7 | impl fmt::Debug for Table {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `std`:
- impl<K, V, S> std::fmt::Debug for std::collections::HashMap<K, V, S>
where K: std::cmp::Eq, K: std::hash::Hash, K: std::fmt::Debug, V: std::fmt::Debug, S: std::hash::BuildHasher;
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> src/main.rs:7:1
|
7 | impl fmt::Debug for Table {
| ^^^^^^^^^^^^^^^^^^^^^^^^^ impl doesn't use types inside crate
|
= note: the impl does not reference only types defined in this crate
= note: define and implement a trait or new type instead
...
ふむ、HashMap
がもとから持っているDebug
トレイトの実装とぶつかってコンパイルが通りませんでした。そりゃそうか。
そうだラッパーをかませよう
Table
というのが構造体ではなく、標準ライブラリのHashMap
へのエイリアスだったのが問題だったので、Table
に対してラッパーを間に挟めばいいのでは・・・。
struct TableWrapper {
table: Table,
}
これに合わせてほかも少し変えておきます。
- impl fmt::Debug for Table {
+ impl fmt::Debug for TableWrapper {
- for (artist, works) in self {
+ for (artist, works) in &self.table {
Debug
トレイトでオブジェクトの所有権を奪うわけにはいかないので、しれっと借用に変更しています(&self.talbe
の部分)。コンパイルはというと・・・。
$ cargo build
...
warning: struct is never constructed: `TableWrapper`
--> src/main.rs:6:1
|
6 | struct TableWrapper {
| ^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(dead_code)] on by default
ふむ、とりあえず通りましたね。
立ちはだかる壁
さて、それではもとの実装を置き換えて行きましょう。Table::new()
を置き換えるために、TableWrapper::new()
を実装しておきます。
impl TableWrapper {
fn new() -> TableWrapper {
TableWrapper {
table: Table::new(),
}
}
}
準備を済ませたぞ。オブジェクトを変更するために、
- let mut table = Table::new();
+ let mut table = TableWrapper::new();
と書き換えてからの、いざぅt。
$ cargo build
...
error[E0599]: no method named `insert` found for type `TableWrapper` in the current scope
--> src/main.rs:41:11
|
6 | struct TableWrapper {
| ------------------- method `insert` not found for this
...
41 | table.insert(
| ^^^^^^
...
ハイ、たくさんエラーが出ます。それもそのはず、ラッパーからだとHashMap
のinsert
を直接呼び出せないですよね。
Derefを実装してやれば・・・
The bookのどこかで見かけたのですが、Rustの演算子.
は実はかなりいろいろなことをやってくれるらしいです。もしTableWrapper
がinsert
を実装していなくても、Deref
トレイトを実装していればそちらを探しに行ってくれるそうです。
Derefで?
残念ながら小生はこのあたり、まだうまい説明が出来ないので興味がある方は[こちら](Implicit Deref Coercions with Functions and Methods)をご参照ください。
Derefを実装
use std::ops::Deref;
impl Deref for TableWrapper {
type Target = Table;
fn deref(&self) -> &Table {
&self.table
}
}
を実装します。もうshow
関数は使えないので、今回のゴールである形に書き換えておきます。
- show(table);
+ println!("{:?}", table);
コンパイルすると・・・。
cargo build
...
warning: variable does not need to be mutable
--> src/main.rs:48:9
|
48 | let mut table = TableWrapper::new();
| ----^^^^^
| |
| help: remove this `mut`
|
= note: #[warn(unused_mut)] on by default
error[E0596]: cannot borrow data in a `&` reference as mutable
--> src/main.rs:49:5
|
49 | table.insert(
| ^^^^^ cannot borrow as mutable
...
ぐぬぬ。
DerefMutを実装
だいぶ近いところまで来た気はするのですが、Deref
はimmutableな参照を返すので、実装すべきはDerefMut
トレイトのようでした。
- use std::ops::Deref;
+ use std::ops::{Deref, DerefMut};
impl DerefMut for TableWrapper {
fn deref_mut(&mut self) -> &mut Table {
&mut self.table
}
}
これでようやくビルドが通るようになりました。
まとめ
Debug
トレイトを実装してみたかっただけなのにだいぶ遠回りしてしまいました。ただ、コンパイラが優しいので、ひとつずつ手直ししていけばゴールにたどり着けるかもしれません。ただ、基本的にずっと怒られっぱなしなので、やっぱりRustってM気質な人向けの言語・・・(知らんけど。
コードの全体図
最終的に次のようなコードになりました。
use std::collections::HashMap;
use std::fmt;
use std::ops::{Deref, DerefMut};
type Table = HashMap<String, Vec<String>>;
struct TableWrapper {
table: Table,
}
impl TableWrapper {
fn new() -> TableWrapper {
TableWrapper {
table: Table::new(),
}
}
}
impl Deref for TableWrapper {
type Target = Table;
fn deref(&self) -> &Table {
&self.table
}
}
impl DerefMut for TableWrapper {
fn deref_mut(&mut self) -> &mut Table {
&mut self.table
}
}
impl fmt::Debug for TableWrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (artist, works) in &self.table {
writeln!(f, "works by {}:", artist)?;
for work in works {
writeln!(f, " {}", work)?;
}
}
Ok(())
}
}
fn main() {
...
println!("{:?}", table);
}