В Rust 1.29.0 один из моих тестов начал сбой. Мне удалось получить странный баг к этому примеру:
#[derive(Clone, Debug)]
struct CountDrop<'a>(&'a std::cell::RefCell<usize>);
struct MayContainValue<T> {
value: std::mem::ManuallyDrop<T>,
has_value: u32,
}
impl<T: Clone> Clone for MayContainValue<T> {
fn clone(&self) -> Self {
Self {
value: if self.has_value > 0 {
self.value.clone()
} else {
unsafe { std::mem::uninitialized() }
},
has_value: self.has_value,
}
}
}
impl<T> Drop for MayContainValue<T> {
fn drop(&mut self) {
if self.has_value > 0 {
unsafe {
std::mem::ManuallyDrop::drop(&mut self.value);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_drops() {
let n = 2000;
let drops = std::cell::RefCell::new(0usize);
let mut slots = Vec::new();
for _ in 0..n {
slots.push(MayContainValue {
value: std::mem::ManuallyDrop::new(CountDrop(&drops)),
has_value: 1,
});
}
unsafe { std::mem::ManuallyDrop::drop(&mut slots[0].value); }
slots[0].has_value = 0;
assert_eq!(slots.len(), slots.clone().len());
}
}
Я знаю, что код выглядит странно; это все вырвано из контекста. Я воспроизвел эту проблему с cargo test
на 64-битном Ubuntu на Rust 1.29.0. Друг не смог воспроизвести на Windows с той же версией Rust.
Другие вещи, которые останавливают воспроизведение:
- Понижение
n
ниже ~ 900. - Не выполняйте пример из
cargo test
. - Замена
CountDrop
элемента сu64
. - Использование версии Rust до 1.29.0.
Что здесь происходит? Да, MayContainValue
может иметь неинициализированный элемент, но это никогда не используется.
Мне также удалось воспроизвести это на play.rust-lang.org.
Меня не интересуют "решения", которые связаны с реорганизацией MayContainValue
безопасным способом с помощью Option
или enum
, я использую ручное хранилище и занятую/незанятую дискриминацию по уважительной причине.