Generate const static strs

Hello Rustaceans!

Please, is there a way to quickly generate SPEC_IEC from SPEC_SI2? I just wanted to include "i" at the end of each prefix.
I've tried const expressions and macro_rules without success.
Thank you!

const M: usize = 11;
const SPEC_SI: [&str; M] = ["", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];
const SPEC_SI2: [&str; M] = const {
    let mut spec = SPEC_SI;
    spec[1] = "K"; // only k is different from SI (1000).
    spec
};
const SPEC_IEC: [&str; M] = [
    "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi", "Ri", "Qi",
];

Playground: Rust Playground

You can do that with paste and [std::stringify!] macros: playground

#![allow(dead_code)]

extern crate paste;

macro_rules! make_spec {
    ($si_name:ident, $iec_name:ident, $($prefix:ident),*) => {
        #[allow(non_snake_case)]
        const $si_name: [&str; 1 $(+ {let $prefix = 1; $prefix})*] = [
            "", $(::std::stringify!($prefix)),*
        ];
        #[allow(non_snake_case)]
        const $iec_name: [&str; 1 $(+ {let $prefix = 1; $prefix})*] = [
            "", $(::paste::paste!(::std::stringify!([< $prefix:upper i >]))),*
        ];
    };
}

make_spec!(SPEC_SI, SPEC_IEC, k, M, G, T, P, E, Z, Y, R, Q);

fn main() {
    println!("{:?}", SPEC_IEC);
    println!("{:?}", SPEC_SI);
}
3 Likes

I'm not sure this is possible in a const fn style way today. I couldn't find something in const_format crate to do it, though if you wanted to deep dive and invent a method, I'm sure that crate's source code will be helpful :slight_smile:

To get it done, I'd perhaps reach for build scripts. In a build script you are able to write normal rust code that outputs all of your SPEC_* const definitions as Strings.

It would also be possible using macros, I'm not handy enough with them to author off the cuff here on the forum. But I'd look into the syntax for repeats and do something like (I forget the actual Syntax!)

macro_rules define_specs!($spec,)+ => {
    const SPEC_1: [&str, M] = [("$spec",)+];
    const SPEC_1: [&str, M] = [("${spec}i",)+];
}

Hopefully there's a way to insert the i at the right point inside of each output repetition when templating. There is a way to do most things like this, macro_rules are nigh inscrutable, but capable! :smiley:

https://doc.rust-lang.org/reference/macros-by-example.html#repetitions

EDIT: ZyX-I knows what they're talking about more than I do. paste! is the missing link to be able to build a string literal out of pieces. It's a really handy proc macro :slight_smile:

1 Like

Note that 1 $(+ {let $prefix = 1; $prefix})* is far from the best way for counting tokens, it just does not require auxilary macros.

Keep it simple, leave it as two hard-coded arrays. It is infinitely more readable than any macro construction capable of handing prefixes plus an exception for k/Ki.

5 Likes

In this particular case I agree. Usually it's balance between correctness and readability.

I may even keep SPEC_SI2 as just a simple array: list of these prefixes is changed about once per decade or so thus I would read these lists many times before I'll need to change them even once.

If similar list is changed more often, though, then the desire to prevent copy-paste errors may be more important.

Yep, thank you for all the responses. It is more complicated than I anticipated.
The three specs were always simple arrays:

But I'm working on human_repr 2.0 now, and not only I had to change those arrays to include R and Q, but also I'll implement support for the other side of the spectrum: milli, micro, nano, etc.: Metric prefix - Wikipedia for quantities between 0. and 1.
That's what really motivated me to search for some programmatic generation.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.