Я работаю над пользовательским типом, где у меня есть следующие требования:
- Коллекция элементов, которые избегают выделения кучи. Я использую массивы вместо
Vec
.
- Коллекция содержит не копируемые типы
- Реализует
Default
для типов, которые также реализуют Default
- Реализует
From
, чтобы я мог построить его прямо из массива
Моя самая большая проблема - это реализация Default
безопасным и полезным способом. Возможность поддерживать подвижные типы в массиве создала некоторые проблемы. Первоначально я слепо использовал mem::uninitialized()
, за которым следовал цикл for ptr::write(&mut data[index], Element::default())
вызовов, чтобы инициализировать его, но я обнаружил, что если default()
вызов отдельных элементов когда-либо паникует, то он попытается вызвать drop
на всех неинициализированные данные в массиве.
Мой следующий шаг включал использование ящика с нодропом для предотвращения этого. Теперь я больше не вызываю drop
для каких-либо неинициализированных данных, но если какой-либо из элементов действительно паникует на default()
, то те, что были до него, которые были правильно построены, никогда не вызывают drop
.
Есть ли способ сообщить компилятору Rust, что безопасно вызывать drop
для предыдущих элементов массива, которые были правильно построены, или есть другой способ приблизиться к этому?
Для ясности, если один из отдельных людей вызывает Element::default()
панику, я хочу:
- Неинициализированные элементы не вызывают
drop
- Правильно инициализированные элементы вызывают
drop
Я не уверен, что это возможно, основываясь на том, что я прочитал до сих пор, но я хотел проверить.
Этот код показывает, где я нахожусь:
extern crate nodrop;
use nodrop::NoDrop;
struct Dummy;
impl Drop for Dummy {
fn drop(&mut self) {
println!("dropping");
}
}
impl Default for Dummy {
fn default() -> Self {
unsafe {
static mut COUNT: usize = 0;
if COUNT < 3 {
COUNT += 1;
println!("default");
return Dummy {};
} else {
panic!("oh noes!");
}
}
}
}
const CAPACITY: usize = 5;
struct Composite<Element> {
data: [Element; CAPACITY],
}
impl<Element> Default for Composite<Element>
where
Element: Default,
{
fn default() -> Self {
let mut temp: NoDrop<Self> = NoDrop::new(Self {
data: unsafe { std::mem::uninitialized() },
});
unsafe {
for index in 0..CAPACITY {
std::ptr::write(&mut temp.data[index], Element::default());
}
}
return temp.into_inner();
}
}
impl<Element> From<[Element; CAPACITY]> for Composite<Element> {
fn from(value: [Element; CAPACITY]) -> Self {
return Self { data: value };
}
}
pub fn main() {
let _v1: Composite<Dummy> = Composite::default();
}
Детская площадка
Это позволяет гарантировать, что неинициализированные элементы массива не вызывают drop
, но еще не позволяет правильно инициализированным компонентам вызывать drop
(они действуют как неинициализированные компоненты и не вызывают drop
). Я заставляю вызов Element::default()
вызывать панику на более позднем элементе, чтобы показать проблему.
Фактическая выработка
Стандартная ошибка:
Compiling playground v0.0.1 (file:///playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.56 secs
Running `target/debug/playground`
thread 'main' panicked at 'oh noes!', src/main.rs:19:17
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Стандартный вывод:
default
default
default
Предполагаемый выход
Стандартная ошибка:
Compiling playground v0.0.1 (file:///playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.56 secs
Running `target/debug/playground`
thread 'main' panicked at 'oh noes!', src/main.rs:19:17
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Стандартный вывод:
default
default
default
dropped
dropped
dropped