How do I do a once-off initialization of a static string at runtime?

I need to use a string for the entire life of the program. But the string is actually computed at runtime (actually by a separate process, so I get it using std::process::Command). Here's the idea (which doesn't work):

#[macro_use] extern crate lazy_static;

fn main() {
    let a = expensive_to_compute_data();
    println!("{}", a);
    let b = expensive_to_compute_data();
    println!("{}", b);
}

lazy_static! {
    pub static ref DATA: String = String::new();
}

pub fn expensive_to_compute_data() -> &'static str {
    if DATA.is_empty() { // Will only happen once
        let s = "VERY EXPENSIVE COMPUTATION";
        DATA.push_str(s);
    }
    &DATA
}

Is this doable? If so, how?
Playground
This seems to work though:

fn main() {
    let a = expensive_to_compute_data();
    println!("{}", a);
    let b = expensive_to_compute_data();
    println!("{}", b);
}

static mut DATA: &str = "";

pub fn expensive_to_compute_data() -> &'static str {
    unsafe {
        if DATA.is_empty() {
            DATA = get_data();
        }

        DATA
    }
}

pub fn get_data() -> &'static str {
    "VERY EXPENSIVE COMPUTATION"
}

Playground

1 Like

If you really need a &'static, you might consider Box in std::boxed - Rust

1 Like

I solved it like this:

#[macro_use]
extern crate lazy_static;

use std::sync::Mutex;

lazy_static! {
    static ref DATA: Mutex<Vec<u8>> = Mutex::new(vec![]);
}

fn main() {
    expensive_initialize_data()   ;
    let a = get_data();
    println!("{}", a);
    let b = get_data();
    println!("{}", b);
}

pub fn expensive_initialize_data() {
    let mut b = vec![65, 66, 67, 97, 98, 99];
    DATA.lock().unwrap().append(&mut b);
}


pub fn get_data() -> String {
    String::from_utf8(DATA.lock().unwrap().to_vec()).unwrap()
}

The downside is the mutex & conversion cost at each get_data() call; but this is nothing compared to the expense of initializing, so doesn't matter to me.

There's also std::once::Once that doesn't have to be global.

I must be missing an important aspect of your problem, but why couldn't some variation of this work ?

lazy_static! {
    pub static ref DATA: String = expensive_to_compute_data();
}

pub fn expensive_to_compute_data() -> String {
    // Will only happen once
    let s = String::from("VERY EXPENSIVE COMPUTATION");
    s
}

And then you use &DATA to get a &str.

2 Likes

@HadrienG you didn't miss a thing! That's exactly what I needed. I had forgotten that the function would only be called the first time it was needed, i.e., at runtime.

Thanks!

2 Likes

In my experience &DATA to get a &str doesn't work, you need to do *DATA to get a String. Weird...

1 Like

This works for me:

use lazy_static::lazy_static;

lazy_static! {
    pub static ref DATA: String = expensive_to_compute_data();
}

fn expensive_to_compute_data() -> String {
    // Will only happen once
    let s = String::from("VERY EXPENSIVE COMPUTATION");
    s
}

fn get_str_ref() -> &'static str {
    &DATA
}

fn main() {
    println!("{}", get_str_ref());
}

However, it doesn't work if I directly put a &DATA in the println statement, because the implicit coercion to &str is not performed in that case. Maybe that's what you observed?

1 Like