How to create a static vec within a function?


#1

Yes, this question has been asked before. I haven’t found a good search phrase for an answer. First an example in C…

malloc()/free() is used on pmsg in the mangled code below so that pmsg survives calls to foo(). I would like to do the equivalent in Rust.

LRESULT def foo(UINT message) {
    static int line_count ;
    static MSG *pmsg = NULL ;
    int i;

    switch (message) {
        case WM_CREATE:
        case WM_DISPLAYCHANGE:
            line_count = GetSystemMetrics (SM_CYMAXIMIZED) ;

            if (pmsg)
                free (pmsg) ;
            pmsg = malloc (line_count * sizeof (MSG)) ;
            return 0 ;
                  
        case WM_KEYDOWN:  // Rearrange storage array and store new message
            for (i = line_count - 1 ; i > 0 ; i--) {  
                pmsg[i] = pmsg[i - 1] ;
            }
            pmsg[0].message = message 
            break ;

        case WM_PAINT: // do something with pmsg
           ...
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

Somehow I’d like to create something like

static mut MSG_VEC: Vec<MSG> = Vec::new();

within a function so that MSG_VEC survives between function calls. Can someone suggest how?

If this is not possible then maybe I need to emulate the C code more closely and use really unsafe code with raw pointers + malloc/free. Not necessarily with the unfreed pmsg on program exit! How do I do that?


#2

You can try using thread_local since you appear to be on a Windows GUI thread/event loop:

use std::cell::RefCell;

fn foo() {
    thread_local! {
        static MSG_VEC: RefCell<Vec<i32>> = RefCell::new(Vec::new());
    }
    MSG_VEC.with(|v| v.borrow_mut().push(1));
}

#3

Excellent. That works. Thanks.

I seriously doubt if I’d have found that solution without assistance. I can make it work, but I don’t understand what’s happening behind the scenes. Time to watch some more Rust tutorials on YouTube…


#4

This puts the Vec into a TLS slot. You can read more about Rust’s TLS API here.

The RefCell wrapper is to allow interior mutability of the Vec - the TLS API (i.e. with()) does not give mutable access to the underlying value.

If you have any questions on this stuff, ask away.


#5

I can push() and pop() without any issues.

I’ve guessed at various methods to iterate the Vec but the compiler gives “lifetime” or “cannot move out of borrowed content” messages.

How do I iterate through the Vec in TLS ? Presumably enumeration is easy once iter() or its ilk is working.


#6
MSG_VEC.with(|v| {
        // if you want immutable iteration
        v.borrow().iter().for_each(|i| println!("{}", i));
        // if you want mutable iteration
        v.borrow_mut().iter_mut().for_each(|i| *i *= 2);
    });

#7

Thanks. That works. In simple terms all the iter() code on MSG_VEC has to be inside the MSG_VEC.with() scope - which is what I failed to understand initially.


#8

That’s right - the with() method gives you a reference to the value in the TLS. To prevent the user from trying to “stash” this away somewhere, beyond the scope of the with() call, it asks you to provide it with a closure that accepts the reference. The way lifetimes work in Rust is that such a reference always has a fresh lifetime parameter, and will never unify with any other lifetime parameters - the end result is you can’t (safely, at least) retain the reference beyond that callback.