Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

ケント・ベックの「テスト駆動開発」の写経をRustでやってみる(7)

5章「原則をあえて破るとき」に入る。

この章は、クソコードを作って終わり。 ただし、これをどうにかしないうちは家に帰らないことも誓う。

とりあえず、今まで作ったドルとは別にフランを作る。それも単なるコピペで作る。 フラン・・・そんな通貨単位はもはや存在しない。時代を感じる。 あれ?CHFはスイスフランか。まだあるわ。

作ったテストは

#[test]
fn test_franc_multiplication() {
    let five = Franc::new(5);
    assert_eq!(Franc::new(10), five.times(2));
    assert_eq!(Franc::new(15), five.times(3));
}

これを充たすFranc構造体をDollarをコピペして5章は終わり。6章に入ろう。

6章では、このコピペを解消する。 Javaなので、DollarクラスとFrancクラスの親クラスとして Moneyクラスを導入する。

さて、Rustではどうするのだろうか。Moneyトレイトを作ることになる・・・んだと思う。 やってみよう。

Javaオブジェクト指向言語と違って、Rustでは継承がない。 トレイトによってメソッドの実装を引き継がせることは出来るのだが、 フィールドの定義を共有することはできない。 通常、構造体のデータを共有したいのなら、最も単純かつ適切な手段はHas-a、 つまり共有部分を別の構造体にして、それを構造体の要素とすることだろう。 というわけで、要素が1つしかない今現在はそれもあまり意味が無い。

この時点でベストだと考えられるのは、Money構造体を作ってそこから タプル構造体で新しい型としてDollarとFrancを作ることかも知れない。 しかし、それも先の展開を考えるとイマイチな気がする・・・。 とりあえず、timesメソッドの共有を考えてみよう。

Dollarのtimesの実装はこうなっている。

pub fn times(&self, multiplier: i32) -> Dollar {
    Dollar {
        amount: self.amount * multiplier,
    }
}

つまり、Dollerのインスタンスを作ることと、 インスタンスからamountの値を得ることができれば実装できる。

というわけで、Moneyトレイトを作ってみた。

trait Money {
    fn new(i: i32) -> Self;
    fn amount(&self) -> i32;
    fn times(&self, multiplier: i32) -> Self
    {
        Self::new(self.amount() * multiplier)
    }
}

これはコンパイルエラーになる。

the size for values of type `Self` cannot be known at compilation time

doesn't have a size known at compile-time

help: the trait `std::marker::Sized` is not implemented for `Self`
note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
note: the return type of a function must have a statically known size
help: consider further restricting `Self`: ` where Self: std::marker::Sized `

Selfを返すモノを実装してるけど、この関数はコンパイル時にSelfのサイズが 決まらない恐れがあるのでダメだと言っている。 多分、Vecのようなホントに大きさが決まらないモノだとダメなんだろう。 メッセージの最後にSelfをSizedだと制限すれば良いんだと書いてある。なるほど?

こうかな?

trait Money {
    fn new(i: i32) -> Self;
    fn amount(&self) -> i32;
    fn times(&self, multiplier: i32) -> Self
    where
        Self: Sized,
    {
        Self::new(self.amount() * multiplier)
    }
}

おお、いけた。

というわけで、timesの実装は書かなくて良くなったものの、 amountというメソッドを新たに実装する必要が出来た。

#[derive(Debug, Eq, PartialEq)]
pub struct Dollar {
    amount: i32,
}

impl Money for Dollar {
    fn new(i: i32) -> Dollar {
        Dollar { amount: i }
    }

    fn amount(&self) -> i32 {
        self.amount
    }
}

#[derive(Debug, Eq, PartialEq)]
pub struct Franc {
    amount: i32,
}

impl Money for Franc {
    fn new(i: i32) -> Franc {
        Franc { amount: i }
    }
    fn amount(&self) -> i32 {
        self.amount
    }
}

かなりの重複感であり、まだ大分クソコードな感じが強い。イケてない。 ただ、これ以上の対応はもう少し先の実装を進めてからにしてみたい。

というわけで、6章まで終わり。