I see you have posted a new thread where you "hide" the XY-problem. That also means that you haven't been able to solve your problem, so let's try to help you with that.
You haven't shared any of your code / layout, so all I cna do is guess, but I'd say that your situation is the following:
static mut GLOBAL: Option<Thing> = None;
unsafe // Safety: Cannot be called in parallel
fn init (...)
{
let _ = GLOBAL.replace(Thing::new(...));
...
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first ()
{
unsafe { /* Safety: none whatsoever! */ init(...); }
stuff_that_uses_global();
}
#[test]
fn snd ()
{
unsafe { /* Safety: none whatsoever! */ init(...); }
stuff_that_uses_global();
}
}
Which indeed makes your tests suffer from UB since a data race is possible.
You can solve the UB using once_cell
(or lazy_static!
, but for this pattern once_cell
API (the only difference between those two crates) seems more appropriate):
use ::once_cell::sync::OnceCell;
static GLOBAL: OnceCell<Thing> = OnceCell::new();
// Note: Only the first call gets to init the global.
fn init (...)
{
let _ = GLOBAL.set(Thing::new(...));
...
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first ()
{
init(...);
stuff_that_uses_global(); // <-+ may
} // | use
// | the
#[test] // | `GLOBAL`
fn snd () // | value
{ // | initialized
init(...); // -----------------+ here
stuff_that_uses_global();
}
}
So we have solved the UB, but the code still has a logic bug: it can and will suffer from race conditions, whereby if the different test runs initialize the value differently, then all the tests but one (the one that wins the race to initialize GLOBAL
) will be using the wrong global.
If that is your case, this indeed showcases that @Hyeonu was right: you have been using a global variable with thus non-local state in a very limiting way: just imagine how easy it would have been to do let state = init(...);
and then pass &state
around in the test functions!
- This is, by the way, one solution: you could design your API to take a
&'_ State
parameter instead of relying on GLOBAL
to be around, and then, if you really want to, you can define a GLOBAL: OnceCell<State>
that lets you offer a top-level API where that state
parameter is hidden. But within your own internal tests, you wouldn't need to use the global.
Otherwise, the solution here would be to have the tests run in a serialized manner:
use ::once_cell::sync::OnceCell;
static GLOBAL: OnceCell<Thing> = OnceCell::new();
// Note: Only the first call gets to init the global.
fn init (...)
{
GLOBAL.set(Thing::new(...)).expect("GLOBAL already init");
...
}
#[cfg(test)]
mod tests {
use super::*;
use ::serial_test::serial;
#[test]
#[serial]
fn first ()
{
init(...);
::scopeguard::defer!({ drop(GLOBAL.take()); }); // Automatic clean-up
stuff_that_uses_global();
}
#[test]
#[serial]
fn snd ()
{
init(...);
::scopeguard::defer!({ drop(GLOBAL.take()); });
stuff_that_uses_global();
}
}
And there you have it. A zero-unsafe
code that does the right thing, even while testing.