これを見て、なるほど、と思ったので書いてみることにした。

パターン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 for T と From for U

↑では From を実装したことで 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 for U
というのは
TからUへ変換することができる
ということで

それはつまり
TはUになることが出来る
ということで、暗黙的に
Into 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) )
}

こんな感じでしょうか。

广告
将在 10 秒后关闭
bannerAds