const fn f<T>(slot: &mut MaybeUninit<T>, value: Option<T>) {
if let Some(value) = value {
// use value somehow
slot.write(value);
}
}
error[E0493]: destructor of `Option<T>` cannot be evaluated at compile-time
--> src/lib.rs:38:42
|
38 | const fn f<T>(slot: &mut MaybeUninit<T>, value: Option<T>) {
| ^^^^^ the destructor for this type cannot be evaluated in constant functions
We can unsafely duplicate T if the value is Some<T>, and forget the Option<T> to make the compiler happy:
const fn f<T>(slot: &mut MaybeUninit<T>, value: Option<T>) {
if let Some(value) = value.as_ref() {
let value = unsafe { std::ptr::read(value) };
// use value somehow
slot.write(value);
}
std::mem::forget(value);
}
However, depending on what you do with the value a panic could cause a double free. So I thought let's forget the value first:
const fn option_manually_drop<T>(value: Option<T>) -> Option<std::mem::ManuallyDrop<T>> {
let out = match value.as_ref() {
Some(v) => Some(std::mem::ManuallyDrop::new(unsafe { std::ptr::read(v) })),
None => None,
};
std::mem::forget(value);
out
}
const fn f<T>(slot: &mut MaybeUninit<T>, value: Option<T>) {
if let Some(value) = option_manually_drop(value) {
let value = std::mem::ManuallyDrop::into_inner(value);
// use value somehow
slot.write(value);
}
}
Am I overcomplicating this? Is there a better solution/implementation?
use std::mem;
use std::mem::MaybeUninit;
const fn f<T>(slot: &mut MaybeUninit<T>, mut value: Option<T>) {
if value.is_some() {
slot.write(value.take().unwrap());
}
mem::forget(value); // only `mem::forget`s a `None`, but makes compiler happy
}
as something that’s accepted by the compiler. Looking now at your own reply, it looks not completely unlike what you’ve already found, but still maybe it’s slightly simpler.
The problem is that the drop glue for T can run arbitrary code, which doesn't need to be const-safe. You can avoid the error if you guarantee that T doesn't have drop glue. I.e. this compiles:
const fn f<T: Copy>(slot: &mut MaybeUninit<T>, value: Option<T>) {
if let Some(value) = value {
// use value somehow
slot.write(value);
}
}
Is it too restrictive for your use case? Since Drop cannot be called in const contexts, I struggle to think of an example in stable Rust where this would be too limiting.
On nightly, you can use the unstable const Destruct bound to achieve the more general effect:
#![feature(const_destruct, const_trait_impl)]
use std::marker::Destruct;
use std::mem::MaybeUninit;
const fn f<T: const Destruct>(slot: &mut MaybeUninit<T>, value: Option<T>) {
match value {
Some(v) => { slot.write(v); }
None => { },
}
}
Of course, ideally, your code should just work, since no value of T is dropped in the None branch. That requires more precise compiler analysis of drops, which is tracked by #![feature(const_precise_live_drops)].