Expanding const &str arrays

I would like to know if someone has already tackled (and ideally solved) this problem:

// Both of these arrays can contain non-ascii chars
const LEMMAS: [&str; 2] = ["ab", "γδ"];
const ENDINGS: [&str; 3] = ["1", "2", "3"];

// 1) Build the cartesian-product of concatenated strings.
// That is:
const EXPANDED: [&str; 6] = [
    "ab1", "ab2", "ab3", "γδ1", "γδ2", "γδ3",
];

// Maybe with some macro using the arrays?
// const EXPANDED: [&str; 6] = expand!(LEMMAS, ENDINGS);
//
// Using literals would also be fine for me:
// const EXPANDED: [&str; 6] = expand!(["ab", "γδ"], ["1", "2", "3"]);

// 2) Add (potentially non-ascii) titled variants.
// That is:
const EXPANDED_WITH_TITLE: [&str; 12] = [
    // EXPANDED
    "ab1", "ab2", "ab3", "γδ1", "γδ2", "γδ3",
    // And the titled variants
    "Ab1", "Ab2", "Ab3", "Γδ1", "Γδ2", "Γδ3",
]

Any suggestion or idea would be appreciated.

Last I tried I was not able to find a solution that works in const contexts.

1 Like

This macro works for literals (although it is a bit ugly because I let an LLM give me the initial version. I just cannot remember macro_rules syntax...).

macro_rules! carthesian {
    // Recursive case: take the first element from the first list, and for each element in the second list,
    // append the concatenated string to the accumulator. Then recurse with the rest of the first list.
    ( [$first:expr $(, $rest:expr)*], [$($second:expr),* $(,)?]; [$($acc:expr),* $(,)?] ) => {
        carthesian!(
            [ $($rest),* ],
            [$($second),*];
            [
                $($acc,)*
                $(concat!($first, $second)),*
            ]
        )
    };
    // Base case: when no more elements remain in the first list, return the accumulated array.
    ( [], [$($second:expr),* $(,)?]; [$($acc:expr),* $(,)?] ) => {
        [$($acc),*]
    };

    ( [$($first:expr),* $(,)?], [$($second:expr),* $(,)?] ) => {
        carthesian!([ $($first),* ], [$($second),*]; [])
    };
}
1 Like

I’d say, it’s possible:

// Both of these arrays can contain non-ascii chars
const LEMMAS: [&str; 2] = ["ab", "γδ"];
const ENDINGS: [&str; 3] = ["1", "2", "3"];

const EXPANDED_MANUAL: [&str; 6] = ["ab1", "ab2", "ab3", "γδ1", "γδ2", "γδ3"];
const EXPANDED: [&str; 6] = expand!(LEMMAS, ENDINGS);

fn main() {
    assert_eq!(EXPANDED_MANUAL, EXPANDED);
}

(playground)


Unicode-compatible capitalization, maybe not as trivial, you’d probably have to copy some case conversion tables from the standard library internals for a constified implementation of it.. unless some crate already has const-compatible versions of it.

10 Likes

Edit: This implementation is unnecessarily convoluted. Check the next comment for a better approach.


Thank you very much to those who answered.

I tried @Bruecki and @steffahn solutions and they both seemed to work, so I went and attempted to solve the last part.

This is a link to the playground with my solution -- even though it doesn't compile since it has no access to seq-macro. I thought posting the link would be better than slamming the 150 lines here. It does work locally.

I apologize if the code happens to be obscure, I am not sure about what's idiomatic in these cases.

For the context, the problem is:

  • Capitalize (only the first letter) a non-ascii const &str (in my case, only Greek)
  • Apply that to a const array [str; N]
  • Merge it with another const array [str; N]

Tested locally, I get:

const EXPANDED_UP_FINAL: [&str; 4] = with_uppercase_gr!(["αβ1", "γδ2"], 2);
// ["αβ1", "Αβ1", "γδ2", "Γδ2"]

which is what I was looking for.

I'm sure there are many things to improve in that snippet. I would glady read any feedback.

After reading again @steffahn solution, I applied the same ideas for the second part. This is much cleaner than my previous version and does not require seq-macro so I can now finally post a playground that works.

// Both of these arrays can contain non-ascii chars
const LEMMAS: [&str; 2] = ["ab", "γδ"];
const ENDINGS: [&str; 3] = ["1", "2", "3"];

const EXPANDED_MANUAL: [&str; 6] = ["ab1", "ab2", "ab3", "γδ1", "γδ2", "γδ3"];
const EXPANDED: [&str; 6] = expand!(LEMMAS, ENDINGS);

// It is fine if the ascii strings remain as it is.
// For my usecase, LEMMAS and ENDINGS are guaranteed to always be Greek.
const EXPECTED: [&str; 12] = [
    "ab1", "ab1", "ab2", "ab2", "ab3", "ab3", 
    "γδ1", "Γδ1", "γδ2", "Γδ2", "γδ3", "Γδ3",
];
const RESULT: [&str; 12] = expand_with_capitalized!(LEMMAS, ENDINGS);

fn main() {
    assert_eq!(EXPANDED_MANUAL, EXPANDED);
    assert_eq!(EXPECTED, RESULT);
}

(playground)

I will chose his comment as the preferred solution since it solves the first part of my question and pretty much the second part too (I wish I could have seen that earlier!).

Thanks to everyone.

1 Like