I'm working on a custom type where I have the following requirements:
- A collection of elements that avoid heap allocation. I am using arrays instead of a
Vec
. - The collection contains non-copyable types
- Implements
Default
for types that also implementDefault
- Implements
From
so that I can build it straight from an array
My biggest problem is implementing Default
in a safe and useful way. Being able to support movable types in the array has provided some challenges. Initially I blindly used mem::uninitialized()
followed by a for loop of ptr::write(&mut data[index], Element::default())
calls to initialize it, but I found that if the default()
call of the individual elements ever panicked, then it would try to call drop
on all of the uninitialized data in the array.
My next step involved using the nodrop crate to prevent that. I now no longer call drop
on any uninitialized data, but if any of the elements do happen to panic on default()
, then the ones before it which were correctly built never call drop
either.
Is there is any way to either tell the Rust compiler it is safe to call drop
on the previous array elements that were correctly built or is there a different way to approach this?
To be clear, if one of the individual calls to Element::default()
panics, I want:
- Uninitialized elements do not call
drop
- Properly initialized elements do call
drop
I'm not sure it is possible based on what I have read so far, but I wanted to check.
This code shows where I am at:
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();
}
It gets as far as ensuring uninitialized elements of the array don't call drop
, but it doesn't yet allow for properly initialized components to call drop
(they act like the uninitialized components and don't call drop
). I force the Element::default()
call to generate a panic on a later element just to show the issue.
Actual output
Standard Error:
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.
Standard Output:
default
default
default
Intended output
Standard Error:
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.
Standard Output:
default
default
default
dropped
dropped
dropped