Can constants be grouped using structs?

 Hello,

Let's say I have some constants defined like this:

const SPRING: &str = "spring";
const SPRING_WEATHER: &str = "warm";
const SUMMER: &str = "summer";
const SUMMER_WEATHER: &str = "hot";
const FALL: &str = "fall";
const FALL_WEATHER: &str = "rainy";
const WINTER: &str = "winter";
const WINTER_WEATHER: &str = "cold";

This screams for using a struct Season with a name and a weather field. However, I'm using the names in match statements and for this, it appears they need to be constants and not fields of a struct. Is there a way to group constants? Thanks!

Maybe you like grouping them in a module?

mod season {
    pub struct Spring;
    pub struct Summer;
    pub struct Fall;
    pub struct Winter;

    pub trait Season {
        const NAME: &str;
        const WEATHER: &str;
    }

    impl Season for Spring {
        const NAME: &str = "spring";
        const WEATHER: &str = "warm";
    }

    impl Season for Summer {
        const NAME: &str = "summer";
        const WEATHER: &str = "hot";
    }

    impl Season for Fall {
        const NAME: &str = "fall";
        const WEATHER: &str = "rainy";
    }

    impl Season for Winter {
        const NAME: &str = "winter";
        const WEATHER: &str = "cold";
    }
}

use season::*;

fn main() {
    let m = match "spring" {
        Spring::NAME => Spring::WEATHER,
        _ => "no match",
    };

    println!("{m}");
}

Playground.

4 Likes

Why… I just finished my own playground :rofl:

trait Season {
    const NAME: &'static str;
    const WEATHER: &'static str;
}

enum Spring {}
impl Season for Spring {
    const NAME: &str = "spring";
    const WEATHER: &str = "warm";
}

enum Summer {}
impl Season for Summer {
    const NAME: &str = "summer";
    const WEATHER: &str = "hot";
}

enum Fall {}
impl Season for Fall {
    const NAME: &str = "fall";
    const WEATHER: &str = "rainy";
}

enum Winter {}
impl Season for Winter {
    const NAME: &str = "winter";
    const WEATHER: &str = "cold";
}

fn usage_test() {
    let x = "";
    match x {
        Spring::NAME => (),
        _ => (),
    }
}

(playground)

2 Likes

Awesome, thanks! A bit more verbose than I hoped for, but that works.

1 Like

Great, thanks! The struct version has the advantage of allowing to instantiate a Season, though.

In principle it should be possible to use a const block in the match, but it seems that's still an unstable feature.

#![feature(inline_const_pat)]

struct Season {
    name: &'static str,
    weather: &'static str,
}

const SPRING: Season = Season {
    name: "spring",
    weather: "warm",
};

pub fn usage_test(s: &str) {
    match s {
        const { SPRING.name } => (),
        _ => (),
    }
}
3 Likes

If you're not allergic to macros:

trait Season {
    const NAME: &'static str;
    const WEATHER: &'static str;
}

macro_rules! Seasons {
    ($($name:ident {
        $id1:ident = $id1_str:expr,
        $id2:ident = $id2_str:expr $(,)?
    })*) => {
        $(
        enum $name {}
        impl Season for $name {
            const $id1: &'static str = $id1_str;
            const $id2: &'static str = $id2_str;
        })*
    };
}

Seasons! {
    Spring {
        NAME = "spring",
        WEATHER = "warm"
    }

    Summer {
        NAME = "summer",
        WEATHER = "hot"
    }

    Fall {
        NAME = "fall",
        WEATHER = "rainy"
    }

    Winter {
        NAME = "winter",
        WEATHER = "cold"
    }
}

Definitely useful. But why can't we just use SPRING.name as a pattern?

Thanks, I'll keep that in mind if the number of items in my actual code, which isn't about seasons, continues to grow.

I'm not the language designers, but in the general case, patterns work like expressions but backwards, so allowing SPRING.name would be saying that there is a . operator in patterns such that season.name also means something. But it can't bind season because it doesn’t have the weather field, so it would have to be restricted only to comparing to constants, which is weird. And, we have the const {} syntax with a clearly defined meaning applicable here, which just doesn’t have all the details considered yet.

Also, it’s just not especially common; this thread is the first time I’ve ever seen someone wanting to match against a constant’s struct field. Matches of this character would usually involve an enum instead of a string comparison, and you can use a macro library like strum to do the matching that creates the enum in the first place. If you didn’t want to use the macro, it’s still only a few lines to declare the string-to-enum conversion manually.

use std::str::FromStr;

#[derive(Clone, Copy, Debug, strum::EnumString, strum::Display)]
#[strum(serialize_all = "kebab-case")]
enum Season {
    Spring,
    Summer,
    Fall,
    Winter,
}

impl Season {
    fn weather(self) -> &'static str {
        match self {
            Season::Spring => "warm",
            Season::Summer => "hot",
            Season::Fall => "rainy",
            Season::Winter => "cold",
        }
    }
}

fn main() {
    let season = Season::from_str("spring").unwrap();
    println!("{season} is {}", season.weather());
}

My thoughts as well. Why not just use an enum instead of a string constant?

I'm parsing input by matching pieces of input to string constants. Once it matches, the result will be an enum.

The :: operator can (and should) be used in match patterns - and in my mind, . is almost the same. Indeed, . is used instead of :: in various languages, including Scala (and Python?) where it can be used in matches.

I'm mostly concerned about matches of input such as this one.