4
2

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 5 years have passed since last update.

Debug traitを実装するまでの道のり

Posted at

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トレイトがあるので、それを実装してやれば・・・という気の迷いを起こしたのがすべてのことの始まりです。まぁ、TableHashMap<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(
   |           ^^^^^^

...

ハイ、たくさんエラーが出ます。それもそのはず、ラッパーからだとHashMapinsertを直接呼び出せないですよね。

Derefを実装してやれば・・・

The bookのどこかで見かけたのですが、Rustの演算子.は実はかなりいろいろなことをやってくれるらしいです。もしTableWrapperinsertを実装していなくても、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);
}
4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?