Static variable with an empty vector of `Cell`

Hello, I need a static variable with a dummy value of !Sync type, but the type is !Sync because it has a vector of !Sync elements, so if the vector is empty then the value of a type is Sync. Is there some way / crate to express it without me writing unsafe?

pub struct Foo(pub Vec<Cell<u8>>);

// No way to get `&mut Foo` thus we can't append elements thus no `Cell` thus `Sync`.
static FOO: Foo = Foo(Vec::new());

fn bar(foo: &Foo) {
    for element in foo.0.iter() {
        element.update(|x| x.wrapping_add(1));
    }
}

bar(&'static FOO); // It is fine to use that dummy across the threads

I don't believe there is such a way.

if it was a dummy value, you can just use a const instead of a static --Vec::new() has been a const fn for every long.

pub struct Foo(pub Vec<Cell<u8>>);

const FOO: Foo = Foo(Vec::new());

fn bar(foo: &Foo) {
    for element in foo.0.iter() {
        element.update(|x| x.wrapping_add(1));
    }
}

bar(&FOO);

My question wasn't clear enough, I need exactly &'static Foo as in example, const won't do as the temporary won't get promoted to 'static.

how about this:

const FOO: &'static Foo = &Foo(Vec::new());

Uhhhh ....

use std::cell::Cell;

const FOO: &'static Vec<Cell<u8>> = &Vec::new();

fn get_foo() -> &'static Vec<Cell<u8>> {
    FOO
}

fn main() {
    let jh = std::thread::spawn(|| {
        let f = get_foo();
        println!("{:p}", f);
    });
    
    let f = get_foo();
    println!("{:p}", f);
    
    jh.join().unwrap();
}
0x6147cd08f7b0
0x6147cd08f7b0

This seems unsound to me. I got a shared reference to the same value from two threads in parallel.

1 Like

I guess this is:

I tried this and understood that my use case cannot be reduced to simply Vec<Cell<u8>>. I have AtomicU8 alongside this Vec - with static it would still be sound as that atomic is Sync and it has exact place.

I was referring to that compiler error, that is also related to this:

Error

error[E0492]: interior mutable shared borrows of temporaries that have their lifetime extended until the end of the program are not allowed

Wow, that looks like something you would want to address before allowing more complex const evaluation.

You mean something like this?

 pub struct Foo {                                                                   
      pub cells: Vec<Cell<u8>,                                                      
      pub counter: AtomicU8,                                       
  }

Yes. My logic doesn't require exactly AtomicU8 there, but Cell<u8> can't be in static.

The compiler sees Vec<Cell<u8>> and concludes Foo: !Sync. It can't verify that Vec is empty.

There's no way to express "this type is Sync when the Vec is empty", because that's a runtime invariant, and Rust's trait system only reasons about types.

You hit this either way:

- Cell<u8> field directly -> !Sync -> can't be in static                             
- Vec<Cell<u8>> field (even empty) -> !Sync -> can't be in static

unsafe impl Sync on a wrapper type is probably the only workaround.

That depiction of "two ways" gave me an idea and it compiles. Basically to create a "ref" type which will have references to atomic and to empty slice of cells. Yeah I would need to use that ref instead of reference which will increase the size, but it is fine.

Thank you all!

1 Like

Sorry for extending this,I am just curious O.O
Did you do something like this?

struct MyRef {                                                                     
    atomic: &'static AtomicU8,                                                  
    cells: &'static [Cell<u8>],  // Always empty at runtime                        
}                                                                                  
                                                                                                         
unsafe impl Sync for MyRef {}                                                      
                                                                                     
static ATOMIC: AtomicU8 = AtomicU8::new(0);                                  
static MY_REF: MyRef = MyRef {                                                     
    atomic: &ATOMIC,                                                               
    cells: &[],                                            
};

Not exatcly. The Foo is the same but I added FooRef<'a> that has all fields of Foo but with references - now code internally uses only FooRef and Foo is basically an allocation that user passes. That doesn't require unsafe.

1 Like