Skip to content

Conversation

@nik-rev
Copy link

@nik-rev nik-rev commented Nov 21, 2025

For me, the rust_i18n::i18n! macro created tens of thousands of hashmaps, and allocated tens of thousands of strings. Both of which were unnecessary. This was such a problem, that it caused stack overflows (on Windows only). This PR makes 2 optimizations:

  • Reduce how HashMaps are created: from one for every pair, to one for every language. Bringing the amount of HashMaps created from 14,000 to 32
  • Reduce how many Strings are created from &'static str by using Cow<'static, str>

Optimization 1: Don't create so many hashmaps. The expansion of i18n! creates a HashMap for every translation pair. I have a lot of translations, so for me thousands of translations were being created. Creating HashMap is expensive, there's no reason to do it once for every pair

In this PR Ive changed the expansion so only a single HashMap is created per language. That brings up the count of HashMap from thousands to a only a couple.

Optimization 2: Don't allocate so many strings. Every call to add_translations allocates both for the key, and each individual inner pair too. Here is the body of the add_translations function:

pub fn add_translations(&mut self, locale: &str, data: &HashMap<&str, &str>) {
    let data = data
        .iter()
        .map(|(k, v)| ((*k).into(), (*v).into()))
        .collect::<HashMap<_, _>>();

    let trs = self.translations.entry(locale.into()).or_default();
    trs.extend(data);
}

It's considered best practice to make your function signature clear. Usually, it's better to require a String rather than &str if you're just going to .to_string() it in the function body.

it also iterates over the entire passed data to clone everything, and then creates another hashmap (the collect)

All of this was causing stack overflow for me on Windows. The expansion of i18n! generated 14,000 HashMaps and spanned over 50,000 lines. With this PR, that's not an issue anymore.

add_translations was changed to this:

pub fn add_translations(
    &mut self,
    locale: Cow<'static, str>,
    data: HashMap<Cow<'static, str>, Cow<'static, str>>,
) {
    let trs = self.translations.entry(locale.into()).or_default();
    trs.extend(data);
}

Most inputs to it are static strings. There's no need to turn them into a String, so they are passed as Cow<'static, str>.

I also added an impl IntoIterator for SimpleBackend.

The example expansion is located at examples/app-egui/src/main.rs

Old expansion

static _RUST_I18N_BACKEND: std::sync::LazyLock<Box<dyn rust_i18n::Backend>> =
    std::sync::LazyLock::new(|| {
        let mut backend = rust_i18n::SimpleBackend::new();
        backend.add_translations(
            "en",
            &std::collections::HashMap::from([("Arthur", "Arthur")]),
        );
        backend.add_translations("en", &std::collections::HashMap::from([("age", "age")]));

Note that then every of the &'static str you see will be .to_string()d in the function call

New expansion

This expansion both has way less overhead, and won't cause any stack overflows

static _RUST_I18N_BACKEND: std::sync::LazyLock<Box<dyn rust_i18n::Backend>> = std::sync::LazyLock::new(||
{
    let mut backend = rust_i18n::SimpleBackend::new();
    backend
        .add_translations(
            ::std::borrow::Cow::Borrowed("en"),
            {
                let map = std::collections::HashMap::with_capacity(278usize);
                map.insert(
                    ::std::borrow::Cow::Borrowed("Arthur", "Arthur"),
                    ::std::borrow::Cow::Borrowed("age", "age"),
                );

@nik-rev nik-rev force-pushed the too-many-hashmaps branch 2 times, most recently from d14a3eb to ca0e6c1 Compare November 21, 2025 14:54
@nik-rev nik-rev marked this pull request as draft November 21, 2025 15:34
@nik-rev nik-rev marked this pull request as ready for review November 21, 2025 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant