Dealing with thread-unsafe initialization of C libraries

I would like to bind to the ultra-fast chacha-opt library. However, it has an initialization function that isn't thread safe. I could modify the library so that its initialization is thread-safe, but I would prefer to avoid modifying it. How can this be handled?

You can use std::sync::Once to add synchronization on top of chacha-opt's initialization function:

pub fn init() {
    static ONCE: Once = ONCE_INIT;

    ONCE.call_once(|| {
        // you'll also want do do something with the failure case here
        unsafe { chacha_startup(); }
    }
}

How do I prevent users from using the library before it has been initialized?

That's easy. Just require the user to pass an witness to the initialization to every function.

Alternatively, simply call init in every entry point function in your API if the number of those is small enough.

Won't work: the init function must be complete before any thread can use
the library.

Once::call_once will block if another thread is already running it. If you get past that, your initialization should be complete.