Это что-то спорная тема, так что позвольте мне начать с объяснением моего случая использования, а затем говорить о реальной проблеме.
Я нахожу, что для кучи небезопасных вещей важно убедиться, что вы не просачиваете память; это на самом деле довольно легко сделать, если вы начнете использовать transmute()
и forget()
. Например, передавая экземпляр в коробке на C-код в течение произвольного промежутка времени, затем извлекая его обратно и "воскрешая его", используя transmute
.
Представьте, что у меня есть безопасная оболочка для такого API:
trait Foo {}
struct CBox;
impl CBox {
/// Stores value in a bound C api, forget(value)
fn set<T: Foo>(value: T) {
// ...
}
/// Periodically call this and maybe get a callback invoked
fn poll(_: Box<Fn<(EventType, Foo), ()> + Send>) {
// ...
}
}
impl Drop for CBox {
fn drop(&mut self) {
// Safely load all saved Foo here and discard them, preventing memory leaks
}
}
Для проверки этого на самом деле не происходит утечка какой-либо памяти, я хочу, чтобы некоторые тесты были такими:
#[cfg(test)]
mod test {
struct IsFoo;
impl Foo for IsFoo {}
impl Drop for IsFoo {
fn drop(&mut self) {
Static::touch();
}
}
#[test]
fn test_drops_actually_work() {
guard = Static::lock(); // Prevent any other use of Static concurrently
Static::reset(); // Set to zero
{
let c = CBox;
c.set(IsFoo);
c.set(IsFoo);
c.poll(/*...*/);
}
assert!(Static::get() == 2); // Assert that all expected drops were invoked
guard.release();
}
}
Как вы можете создать этот тип статического одноэлементного объекта?
Он должен использовать блокировку защиты стиля Semaphore
, чтобы гарантировать, что несколько тестов не выполняются одновременно, а затем без всякого доступа к некоторому статическому изменяемому значению.
Я подумал, что возможно эта реализация будет работать, но практически говоря, она терпит неудачу, потому что изредка условия гонки приводят к дублированию выполнения init
:
/// Global instance
static mut INSTANCE_LOCK: bool = false;
static mut INSTANCE: *mut StaticUtils = 0 as *mut StaticUtils;
static mut WRITE_LOCK: *mut Semaphore = 0 as *mut Semaphore;
static mut LOCK: *mut Semaphore = 0 as *mut Semaphore;
/// Generate instances if they don't exist
unsafe fn init() {
if !INSTANCE_LOCK {
INSTANCE_LOCK = true;
INSTANCE = transmute(box StaticUtils::new());
WRITE_LOCK = transmute(box Semaphore::new(1));
LOCK = transmute(box Semaphore::new(1));
}
}
Обратите внимание, что в отличие от обычной программы, в которой вы можете быть уверены, что ваша точка входа (основная) всегда работает в одной задаче, тестовый бегун в Rust не предлагает какой-либо отдельной точки входа, как это.
Другое, очевидно, чем указание максимального количества задач; учитывая десятки тестов, только небольшая потребность в этом, и это медленно и бессмысленно ограничивать пул тестовых задач одним только для этого случая.