quinnwencn / blog

Apache License 2.0
0 stars 0 forks source link

[RustLearning] 宏的定義和使用 #31

Open quinnwencn opened 1 month ago

quinnwencn commented 1 month ago

解決Space Age問題,並總結宏的定義和使用

quinnwencn commented 1 month ago

題解

在Rust語言中,宏是一個非常重要的部分。基於宏,我們可以編寫通用代碼、元編程、甚至是代碼生成。在Exercise的Space Age題目中,使用宏就可以生成各個星球的結構體定義和特定函數代碼的生產,從而簡化代碼下面,是我編寫的Space Age的解題代碼

// The code below is a stub. Just enough to satisfy the compiler.
// In order to pass the tests you can add-to or change any of this code.

#[derive(Debug)]
pub struct Duration (f64);

impl From<u64> for Duration {
    fn from(s: u64) -> Self {
        Duration (s as f64 / 31557600 as f64)
    }
}

pub trait Planet {
    fn period() -> f64 {
        1.0
    }

    fn years_during(d: &Duration) -> f64 {
        d.0 / Self::period()
    }
}

macro_rules! impl_planet_for {
    ($name:ident, $period:expr) => {
        pub struct $name;
        impl Planet for $name {
            fn period() -> f64 {
                $period
            }
        }
    }
}

impl_planet_for!(Mercury, 0.2408467);
impl_planet_for!(Venus, 0.61519726);
impl_planet_for!(Earth, 1.0);
impl_planet_for!(Mars, 1.8808158);
impl_planet_for!(Jupiter, 11.862615);
impl_planet_for!(Saturn, 29.447498);
impl_planet_for!(Uranus, 84.016846);
impl_planet_for!(Neptune, 164.79132);

可以看到,我在代碼中使用了宏來定義結構體和period這個函數,代碼相對比較整潔。當然,這是我第二版代碼,第一版代碼沒有使用宏,這裏展示第一版的代碼,來說明宏的重要性:

/ The code below is a stub. Just enough to satisfy the compiler.
// In order to pass the tests you can add-to or change any of this code.
#[derive(Debug)]
pub struct Duration (u64);
impl From<u64> for Duration {
    fn from(s: u64) -> Self {
        Duration (s)
    }
}
pub trait Planet {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64
    }
}
pub struct Mercury;
pub struct Venus;
pub struct Earth;
pub struct Mars;
pub struct Jupiter;
pub struct Saturn;
pub struct Uranus;
pub struct Neptune;
impl Planet for Mercury {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64  / 0.2408467 / 31557600.0
    }
}
impl Planet for Venus {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64 / 0.61519726 / 31557600.0
    }
}
impl Planet for Earth {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64 / 1.0 / 31557600.0
    }
}
impl Planet for Mars {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64 / 1.8808158 / 31557600.0
    }
}
impl Planet for Jupiter {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64 / 11.862615 / 31557600.0
    }
}
impl Planet for Saturn {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64 / 29.447498 / 31557600.0
    }
}
impl Planet for Uranus {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64 / 84.016846 / 31557600.0
    }
}
impl Planet for Neptune {
    fn years_during(d: &Duration) -> f64 {
        d.0 as f64 / 164.79132 / 31557600.0
    }
}

宏的定義和使用

通過上述代碼的對比,我們可以看到宏對於代碼生成的重要性。這裏我根據我的學習介紹Rust中宏的定義和使用。在Rust中,宏的定義通常使用macro_rules! macro_name { ... }代碼塊包含,其中的macro_name就是宏的名稱。我們以題解中的宏為例:

macro_rules! impl_planet_for {
    ($name:ident, $period:expr) => {
        pub struct $name;
        impl Planet for $name {
            fn period() -> f64 {
                $period
            }
        }
    }
}

impl_planet_for是宏的名字,該宏定義了一個結構體和該結構體實現的一個trait。結構體的名稱由宏的第一個參數定義,第二個參數是trait的參數。這裏我們看到,name的類型是ident,全稱叫identififiers,即標識符;第二個參數period的類型是expr,即表達式,會產生一個值。宏的參數都以美元符號$開始。 除了identexpr外,Rust中的標識符還有: