Using Mutex< RefCell< ... > >

I am having trouble putting my own struct inside Mutex< RefCell< ... > >. Here is an example main.rs file that demonstrates the problem:

use std::sync::Mutex;
use std::cell::RefCell;
// This program runs fine.
//
// If I remove the /* and */ lines
// and change: < String > -> < MyString >
// and change: String::new -> MyString::new
//
// I get the error: 
// cannot call non-const associated function `MyString::new` in statics 
/*
#[derive(Debug)]
struct MyString {
    value : String
}
impl MyString {
    pub fn new() -> Self {
        Self {
            value : "".to_string()
        }
    }
}
*/
static MUTEX : Mutex< RefCell< String > > = 
    Mutex::new( RefCell::new( String::new() ) );
//
fn main() {
    println!( "MUTEX = {:?}", MUTEX );
}
1 Like

a static can only be initialized in const context, so your MyString::new() must be a const function.

if your constructor cannot be const, then you simply cannot put it in a static variable with Mutex alone, try LazyLock instead, which initialize the variable at runtime lazily when it is first accessed.

btw, unless you are on embedded targets and uses something like critical_section::Mutext, you don't need RefCell inside a std::Mutex, since Mutex already has interior mutability functionality, so just Mutex<String> is enough.

2 Likes

I currently have thread local data that cashes some information for re-use.

This data can be quite large, so would be better if all the threads could share this information, when it is not being modified; i.e., if I did not need a separate copy for each thread.

I would also like to be able to go back and forth between adding to the global data and using it.
How does one normally go about this in rust ?

1 Like

Is this something like a configuration that is occasionally updated? Does only one thread write the data? Can you afford the memory to have two copies exist at once when it is being re-written? If so, arc-swap might be a good solution.

But if your problem does not have those characteristics, then RwLock would likely be a reasonable choice — it is like Mutex but allows multiple simultaneous readers.

Whichever type you use for mutability, as @nerditation already wrote, you can use LazyLock to initialize static data that can't be const initialized. Or for some applications, a static RwLock<Option<MyString>> might be the right choice — you can initialize it with None.

1 Like

The RwLock seems to be the proper choice for my purpose. I will give it a try.Thanks.

It almost worked for my application, but I need it to be static and I need it to have a HashMap in it. The following program does not compile when you include the HashMap:

use std::sync::RwLock;
use std::collections::HashMap;
//
// static RW : RwLock< HashMap<String,usize > > = RwLock::new( HashMap::new() );
// If I uncomment the line above, I get the following error: cannot call 
// non-const associated function `HashMap::<String, usize>::new` in statics
//
#[derive(Debug)]
struct MyStruct { 
    key   : Vec<String> ,
    value : Vec<usize>  ,
}
impl MyStruct {
    pub const fn new() -> Self {
        Self {
            key   : Vec::new(),
            value : Vec::new(),
        }
    }
}
static RWLOCK : RwLock< MyStruct > = RwLock::new( MyStruct::new() );
//
//
fn add_to_rwlock(key : String, value : usize)
{   
    // try_write
    let try_write = RWLOCK.try_write();
    println!( "try_write = {:?}", try_write );
    if try_write.is_err() {
        panic!( "attempt to write while a read or write was active" )
    }
    //
    // try_write_unwrap
    let my_struct  = &mut try_write.unwrap();
    my_struct.key.push(key);
    my_struct.value.push(value);
}   
//
fn main() {
    println!( "RWLOCK = {:?}", RWLOCK );
    add_to_rwlock("one".to_string(), 1);
    println!( "RWLOCK = {:?}", RWLOCK );
    add_to_rwlock("two".to_string(), 2);
    println!( "RWLOCK = {:?}", RWLOCK );
}

You can use LazyLock<RwLock<MyStruct>> to handle the non-const initialization.

The following program also results in the non-const function error:

use std::sync::LazyLock;
use std::sync::RwLock;
use std::collections::HashMap;
//
#[derive(Debug)]
struct MyStruct {
    map   : HashMap<String, usize> ,
}
impl MyStruct {
    pub const fn new() -> Self {
        Self { map   : HashMap::new() }
    }
}
static LAZY_RWLOCK : LazyLock< RwLock< MyStruct > > =
    LazyLock::new(|| RwLock::new( MyStruct::new() ) );
//
fn main() {
    println!( "LAZY_RWLOCK = {:?}", LAZY_RWLOCK );
} 

Now you don't need this function to be const.

The following works for me and tests the LazyLock < RwLock < ... > > construct for my intended purpose:

use std::sync::LazyLock;
use std::sync::RwLock;
use std::collections::HashMap;
//
#[derive(Debug)]
struct MyStruct {
    map   : HashMap<String, usize> ,
}
impl MyStruct {
    pub fn new() -> Self {
        Self { map   : HashMap::new() }
    }
}
static LAZY_RWLOCK : LazyLock< RwLock< MyStruct > > = 
    LazyLock::new(|| RwLock::new( MyStruct::new() ) );
//
fn add_to_lazy_rwlock(key : String, value : usize) {
    //
    // rwlock
    let rwlock = &*LAZY_RWLOCK;
    //
    // my_struct
    let try_write = rwlock.try_write();
    if try_write.is_err() {
        panic!( "attempt to write while a read or write was active" )
    }
    let my_struct  = &mut try_write.unwrap();
    //
    // map
    let map = &mut my_struct.map;
    let option = map.insert(key, value);
    if option.is_some() {
        panic!( "attempt to insert same key twice" );
    }

}
fn main () {
    println!( "LAZY_RWLOCK = {:?}", LAZY_RWLOCK );
    add_to_lazy_rwlock("one".to_string(), 1);
    println!( "LAZY_RWLOCK = {:?}", LAZY_RWLOCK );
    add_to_lazy_rwlock("two".to_string(), 2);
    println!( "LAZY_RWLOCK = {:?}", LAZY_RWLOCK );
}   

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.