Alternative to mutable static

Hi,

I'm trying to use some code from another crate for my custom implementation and stumbled upon the following code:

fn style_to_font<S: BackendTextStyle>(style: &S) -> Font {
    // iced font family requires static str
    static mut FONTS: Lazy<HashSet<String>> = Lazy::new(HashSet::new);

    Font {
        family: match style.family() {
            FontFamily::Serif => font::Family::Serif,
            FontFamily::SansSerif => font::Family::SansSerif,
            FontFamily::Monospace => font::Family::Monospace,
            FontFamily::Name(s) => {
                let s = unsafe {
                    if !FONTS.contains(s) {
                        FONTS.insert(String::from(s));
                    }
                    FONTS.get(s).unwrap().as_str()
                };
                font::Family::Name(s)
            }
        },
        weight: match style.style() {
            FontStyle::Bold => font::Weight::Bold,
            _ => font::Weight::Normal,
        },
        ..Font::DEFAULT
    }
}

Which gives me the following lint:

1  warning: creating a shared reference to mutable static is discouraged
    --> src/backend.rs:331:25
     |
 331 |                     if !FONTS.contains(s) {
     |                         ^^^^^^^^^^^^^^^^^ shared reference to mutable static
     |
     = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html>
     = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
     = note: `#[warn(static_mut_refs)]` on by default

I have looked at the given link, but can not come up with a solution for this. Maybe someone can explain the details and probably give a hint for a better implementation.

Full code:

Here's an option.

    static FONTS: LazyLock<Mutex<HashSet<&'static str>>> = LazyLock::new(|| {
        Mutex::new(HashSet::new())
    });

    // ...

            FontFamily::Name(s) => {
                let mut lock = FONTS.lock().unwrap();
                if !lock.contains(s) {
                    let s = String::leak(String::from(s));
                    lock.insert(s);
                }
                let s = *lock.get(s).unwrap();
                FontFamily::Name(s)
            }

Just remove the mut. If you need to modify the value, wrap it in a Mutex.

static FONTS: Mutex<BTreeSet<String>> = Mutex::new(BTreeSet::new());

Using BTreeSet lets you avoid the LazyLock because BTreeSet::new() is const.

5 Likes

Thank you both, I first tried the solution from @alice, but couldn't make the borrow checker happy, then I combined it with the code from @quinedot and now it works.

Just for my understanding, static gives FONTS a precise location in memory, thus I can share the references to it's contained str? What would happen, if I remove elements from the BTreeSet?

Yes, that's part of what it does.

No, because the Mutex doesn't allow you to keep references to its contents after you release the lock.

This is an example which would result in a use-aftee-free kind of bug if Mutex did allow you to keep references to its contents. However it does not allow that, so when you remove elements from the BTreeSet you can be sure there is nothing referencing them.

1 Like

For completeness and for other readers, would you mind posting the combined code?

Sure,

fn style_to_font<S: BackendTextStyle>(style: &S) -> Font {
    static FONTS: Mutex<BTreeSet<&'static str>> = Mutex::new(BTreeSet::new());

    Font {
        family: match style.family() {
            // ...
            FontFamily::Name(s) => {
                let mut lock = FONTS.lock().unwrap();
                if !lock.contains(s) {
                    let s = String::leak(String::from(s));
                    lock.insert(s);
                }
                let s = *lock.get(s).unwrap();
                font::Family::Name(s)
            }
        },
        weight: match style.style() {
            FontStyle::Bold => font::Weight::Bold,
            _ => font::Weight::Normal,
        },
        ..Font::DEFAULT
    }
}
2 Likes

Thanks!