rust101-fieldlog

03 / 10 · 15 分钟

用 enum 表示天气

漏掉一种天气,程序编不过。

你的项目里会动到:src/model.rs

这一章不新建文件,只改已经有的 src/model.rs。lib.rs 不用动。

天气如果是 String,日志里会同时出现「晴」「晴天」和「sunny」。用 enum 把合法的值定死。地形另写一个 enum:雨可以下在山脊上,也可以下在林子里。

src/model.rs
pub enum Weather {
    Clear,
    Fog,
    Rain,
    Storm,
    Snow,
}

pub enum Terrain {
    Trail,
    Ridge,
    Scree,
    Forest,
    Camp,
}

然后给 DayLog 加上 weather 和 terrain 两个字段。样例里没有雪,match 仍然要写出 Snow。写了 _ 的话,以后新加的天气会静静变成一个错误的词。

在原有字段上补天气和地形。仍然没有 serde。

src/model.rs
/// 一天的记录。距离用米保存,公里只在显示时换算。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DayLog {
    /// `YYYY-MM-DD`。
    pub date: String,
    pub title: String,
    pub distance_m: u32,
    pub minutes: u32,
    pub elevation_m: u32,
    pub weather: Weather,
    pub terrain: Terrain,
    pub note: String,
}

动手

给每种天气一个中文

用 match 返回「晴」「雾」「雨」「暴风雨」「雪」。不要写 _ 分支。

weather_label 的起点
pub fn weather_label(weather: Weather) -> &'static str {
    todo!("weather_label")
}

五种分支,每种一个字符串字面量。返回 &'static str,这些词和程序活得一样久。

terrain_label 是同一写法,用来对照。鞍部那天的天气是「暴风雨」,地形是「碎石坡」。weather_label 可以先留成 todo!()。下面是这一章结束时的 model.rs,测试也在里面。

src/model.rs
/// 天气是有限的几种,不是随意的字符串。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Weather {
    Clear,
    Fog,
    Rain,
    Storm,
    Snow,
}

/// 脚下是什么。和天气分开,因为雨可以下在山脊上,也可以下在林子里。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Terrain {
    Trail,
    Ridge,
    Scree,
    Forest,
    Camp,
}

// region: weather_label
/// 简报里的天气用词。`match` 不写 `_`,漏掉一种天气就编不过。
pub fn weather_label(weather: Weather) -> &'static str {
    match weather {
        Weather::Clear => "晴",
        Weather::Fog => "雾",
        Weather::Rain => "雨",
        Weather::Storm => "暴风雨",
        Weather::Snow => "雪",
    }
}
// endregion: weather_label

pub fn terrain_label(terrain: Terrain) -> &'static str {
    match terrain {
        Terrain::Trail => "小径",
        Terrain::Ridge => "山脊",
        Terrain::Scree => "碎石坡",
        Terrain::Forest => "林地",
        Terrain::Camp => "营地",
    }
}

// region: day_log
/// 一天的记录。距离用米保存,公里只在显示时换算。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DayLog {
    /// `YYYY-MM-DD`。
    pub date: String,
    pub title: String,
    pub distance_m: u32,
    pub minutes: u32,
    pub elevation_m: u32,
    pub weather: Weather,
    pub terrain: Terrain,
    pub note: String,
}
// endregion: day_log

#[cfg(test)]
mod tests {
    use super::*;

    fn meadow() -> DayLog {
        DayLog {
            date: "2026-09-18".to_string(),
            title: "北口草甸".to_string(),
            distance_m: 8400,
            minutes: 140,
            elevation_m: 320,
            weather: Weather::Clear,
            terrain: Terrain::Trail,
            note: "雾停在谷底。".to_string(),
        }
    }

    #[test]
    fn meadow_day_keeps_meters() {
        let day = meadow();
        assert_eq!(day.distance_m, 8400);
        assert_eq!(weather_label(day.weather), "晴");
    }

    #[test]
    fn weather_labels_cover_every_variant() {
        assert_eq!(weather_label(Weather::Clear), "晴");
        assert_eq!(weather_label(Weather::Fog), "雾");
        assert_eq!(weather_label(Weather::Rain), "雨");
        assert_eq!(weather_label(Weather::Storm), "暴风雨");
        assert_eq!(weather_label(Weather::Snow), "雪");
    }
}

检查点

在你的 fieldlog 目录
cargo test model::tests::weather_labels_cover_every_variant

五种天气都有断言。少写一种变体,先是编译失败,不是测试失败。