9章に入る。いよいよ、通貨の導入だ。
その前に、Dollar構造体とFranc構造体の統一を片付けてしまおう。 9章の展開とは違うんだけど、とりあえず、以下の様なテストが通ることを目標にする。
#[cfg(test)] mod tests { use super::*; #[test] fn test_multiplication() { let five_d = Money::dollar(5); assert_eq!(Money::dollar(10), five_d.times(2)); assert_eq!(Money::dollar(15), five_d.times(3)); let five_f = Money::franc(7); assert_eq!(Money::franc(35), five_f.times(5)); assert_eq!(Money::franc(77), five_f.times(11)); } #[test] fn test_equality() { assert_eq!(Money::dollar(5), Money::dollar(5)); assert_ne!(Money::dollar(5), Money::dollar(6)); assert_eq!(Money::franc(7), Money::franc(7)); assert_ne!(Money::franc(7), Money::franc(6)); // 通貨が違うので、一致しない。 assert_ne!(Money::franc(5), Money::dollar(5)); } }
ここにはDollarもFrancも影も形もない。
コードは今はこんな感じ。
trait Money { fn new(i: i32) -> Self; // Factory Method fn dollar(i: i32) -> Dollar { Dollar::new(i) } fn franc(i: i32) -> Franc { Franc::new(i) } fn amount(&self) -> i32; fn times(&self, multiplier: i32) -> Self where Self: Sized, { Self::new(self.amount() * multiplier) } } #[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 } } impl PartialEq<Franc> for Dollar { fn eq(&self, _other: &Franc) -> bool { false } } #[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 } } impl PartialEq<Dollar> for Franc { fn eq(&self, _other: &Dollar) -> bool { false } }
もちろんコンパイルエラーになるはずだ。まずは、エラーの解消を目指そう。
DollarとFrancにある処理をMoney構造体に集める。 ほぼ機械的にできるし、とてもコードが短くなった。
#[derive(Debug, Eq, PartialEq)] pub struct Money { amount: i32, } impl Money { fn dollar(i: i32) -> Money { Money { amount: i } } fn franc(i: i32) -> Money { Money { amount: i } } fn amount(&self) -> i32 { self.amount } fn times(&self, multiplier: i32) -> Money { Money { amount: self.amount() * multiplier, } } }
テストを実行すると、ドルとフランを比較するところでテストに失敗する。 以前は型が違う場合はエラーにしていたんだから、それはそうだ。
というわけで、いよいよここで通貨の導入だ。 本と同じように、文字列で通貨を表現することにしてみよう。
pub struct Money { amount: i32, currency: str, }
しかし、この変更はコンパイルエラーになる。 strはいわゆるC言語の文字列みたいなものなので、サイズが決まらないからだ。
error[E0277]: the size for values of type `str` cannot be known at compilation time --> src/main.rs:11:26 | 11 | fn dollar(i: i32) -> Money { | ^^^^^ doesn't have a size known at compile-time | = help: within `Money`, the trait `std::marker::Sized` is not implemented for `str` = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait> = note: required because it appears within the type `Money` = note: the return type of a function must have a statically known size
そもそも、C言語に倣うならMoneyが持っているのは文字列へのポインタになるはずだ。 というわけで、strへの参照に変えてみる。
pub struct Money { amount: i32, currency: &str, }
しかし、これもダメだ。
error[E0106]: missing lifetime specifier --> src/main.rs:7:15 | 7 | currency: &str, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 5 | pub struct Money<'lifetime> { 6 | amount: i32, 7 | currency: &'lifetime str, |
currencyが指している先の文字列がずっとある保証がないからだ。 エラーメッセージでは、ライフタイムパラメタを入れたらどうかと言われている。
入れてみよう。
#[derive(Debug, Eq, PartialEq)] pub struct Money { amount: i32, currency: &'static str, } impl Money { fn dollar(i: i32) -> Money { Money { amount: i, currency: "USD", } } fn franc(i: i32) -> Money { Money { amount: i, currency: "CHF", } } fn amount(&self) -> i32 { self.amount } fn times(&self, multiplier: i32) -> Money { Money { amount: self.amount() * multiplier, currency: self.currency, } } }
'statuc
は、プログラムの起動中はずっと存在することを示すライフタイムだ。
このコードではcurrencyに入る可能性があるのは、文字列リテラルだけなのでこれでOK。
では、eqメソッドを実装しよう。ついでに、全部のメソッドにpubを付ける。 付けていないと、「使われていないよ」というワーニングが出るのがありがたい。
pub fn eq(&self, other: &Money) -> bool { if self.currency != other.currency { false } else { self.amount == other.amount } }
これでテストも全部通過した。素晴らしい。