これを見て、なるほど、と思ったので書いてみることにした。
パターン1 まずは自分なりに書いてみる
FizzとBuzzとFizzBuzzをそれぞれ以下のように考える
- Fizz
- 3で割り切れるならFizzっていう人
- Buzz
- 5で割り切れるならBuzzっていう人
- FizzBuzz
- FizzとBuzzの組み合わせ
pub struct Fizz {
values: [Option<&'static str>; 3],
}
impl Fizz {
pub fn new() -> Fizz {
Fizz { values: [Some("Fizz"), None, None] }
}
fn get(&self, i: usize) -> Option<&'static str> {
self.values[i%3]
}
}
pub struct Buzz {
values: [Option<&'static str>; 5],
}
impl Buzz {
pub fn new() -> Buzz {
Buzz { values: [Some("Buzz"), None, None, None, None] }
}
fn get(&self, i: usize) -> Option<&'static str> {
self.values[i%5]
}
}
fn main() {
let fizz = Fizz::new();
let buzz = Buzz::new();
for i in 1..50 {
match (fizz.get(i), buzz.get(i)) {
(None, None) => println!("{}", i),
(fizz, buzz) => println!("{}{}", fizz.unwrap_or_default(), buzz.unwrap_or_default()),
}
}
}
割り切れるかどうか、ではなくて、剰余をindexとして使いたかったので、配列として持っている。
本当は定数として、構造体のfieldに持って Buzz::get(i)
のような感じにしたかったけど、
構造体の中で static
や const
はできないらしい。
[修正] 1.20(stable)以降ではAssociated constで実現できる
やるとしたら、こんな感じだろうか。
const FIZZ_VALUES: [Option<&'static str>; 3] = [Some("Fizz"), None, None];
const BUZZ_VALUES: [Option<&'static str>; 5] = [Some("Buzz"), None, None, None, None];
pub struct Fizz;
impl Fizz {
fn get(i: usize) -> Option<&'static str> {
FIZZ_VALUES[i%3]
}
}
pub struct Buzz;
impl Buzz {
fn get(i: usize) -> Option<&'static str> {
BUZZ_VALUES[i%5]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fizzbuzz() {
for i in 1..50 {
match (Fizz::get(i), Buzz::get(i)) {
(None, None) => println!("{}", i),
(fizz, buzz) => println!("{}{}", fizz.unwrap_or_default(), buzz.unwrap_or_default()),
}
}
}
}
パターン2
冒頭で触れた記事のトレイトの使い方になるほど感があったので、マネしてみる。
- FizzとBuzzは、同じ種類と考える
- つまりEnumだ
- Fizz(Buzz)がどう発言するか
- つまり、Displayトレイトだ
- iがどういう時に、Fizzになるか、Buzzになるか
- つまり、Fromトレイトだ
-
x.into()
って書けるとカッコイイ
use std::fmt;
enum FizzBuzz {
Fizz,
Buzz,
FizzBuzz,
Num(u32),
}
impl fmt::Display for FizzBuzz {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&FizzBuzz::Fizz => write!(f, "Fizz"),
&FizzBuzz::Buzz => write!(f, "Buzz"),
&FizzBuzz::FizzBuzz => write!(f, "FizzBuzz"),
&FizzBuzz::Num(i) => write!(f, "{}", i),
}
}
}
impl From<u32> for FizzBuzz {
fn from(n: u32) -> FizzBuzz {
match (n%3, n%5) {
(0, 0) => FizzBuzz::FizzBuzz,
(0, _) => FizzBuzz::Fizz,
(_, 0) => FizzBuzz::Buzz,
_ => FizzBuzz::Num(n),
}
}
}
fn main() {
(1..50)
.map(|x| x.into())
.for_each(|r: FizzBuzz| println!("{}", r) )
}
すこしシンプルになったかな?
Into<U> for T
と From<T> for U
↑では From<T>
を実装したことで into()
が使えるようになった。
でも、ここで疑問
Into を実装するのと、何が違うんだろうか
Fromには
From for U implies Intofor T
from is reflexive, which means that From for T is implemented
と書かれている。
// From implies Into
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> Into<U> for T where U: From<T>
{
fn into(self) -> U {
U::from(self)
}
}
ここかな?
From<T> for U
というのは
TからUへ変換することができる
ということで
それはつまり
TはUになることが出来る
ということで、暗黙的に
Into<U> for T
となることができるはず
っていうことですかね?
つまり、
impl Into<Hoge> for Foo {
…
}
を手で書くことはほとんどなくて、それがやりたい時はFromを実装する。
Into
を使う場面は、型指定で、特定の何かにintoできるもの
というのを表現したい時に使う。
と理解しておけばいいのだろうか?
今回のケースでいうと
fn print_fizzbuzz<T: Into<FizzBuzz>>(x: T) {
println!("{}", x.into())
}
fn main() {
(1..50).for_each(|x| print_fizzbuzz(x) )
}
こんな感じでしょうか。