Here is the code: I want to take the field a of <() as GetConst>::VALUE, if I put the VALUE into global const then everything is good, but the compiler doesn't recognize it in trait const. I have an idea that to take a while mem::forget the rest, but if so I have to forget all of the rest field by hand. Is there a better way? Thanks.
(I have modified the code, the following is the new version)
#![allow(dead_code)]
struct NotCopy(u64);
enum MaybeDrop {
NoDrop(NotCopy),
Drop(String)
}
struct Bundled {
a: MaybeDrop,
b: MaybeDrop,
c: MaybeDrop,
d: MaybeDrop,
// ... many more ...
}
const _VALUE: Bundled = Bundled {
a: MaybeDrop::NoDrop(NotCopy(1234567890)),
b: MaybeDrop::NoDrop(NotCopy(9876543210)),
c: MaybeDrop::NoDrop(NotCopy(2342374982234)),
d: MaybeDrop::NoDrop(NotCopy(393407032974)),
};
trait GetConst {
const VALUE: Bundled = _VALUE;
}
impl GetConst for () {}
// --------------------------
// Not allowed to edit above code
// ok
const _EXTRACT: MaybeDrop = _VALUE.a;
// error[E0493]: destructor of `Bundled` cannot be evaluated at compile-time
const EXTRACT: MaybeDrop = <() as GetConst>::VALUE.a;
btw, if you don't want to use the unsafe raw pointer read , you can mem::replace() it with a dummy value:
const EXTRACT: MaybeDrop = const {
let mut bundled = get();
let dummy = MaybeDrop::NoDrop(NotCopy);
let a = std::mem::replace(&mut bundled.a, dummy);
std::mem::forget(bundled);
a
};
Thanks, but this is unsound. Because the MaybeDrop::Drop is bound to drop, that's why I used Box<()>. It is just because I can guarantee by myself that I won't construct MaybeDrop::Drop in constant context.
Well, Miri didn't complain. What about this very code snippet is unsound?
If you don't want to ensure your invariants manually you can make MaybeDrop a private type and expose only two factory methods pub fn drop() and pub const fn no_drop() to construct the outer, exported type. This way, you cannot create MaybeDrop::Drop from a const context, because its only factory method is not const.
Sorry, the way I express it may not be good. I didn't mean it is unsafe, I mean, your workaround breaks the premise, which makes itself become no sense. My structure definition is
enum MaybeDrop {
NoDrop(NotCopy),
// `Box<T>` implements `Drop` explicitly, it will bound to cause destruction.
Drop(Box<()>)
}
While yours is
enum MaybeDrop<T> {
NoDrop(ManuallyDrop<T>),
// if `T` has no destructor, it will makes `MaybeDrop` have no destructor at all,
// which breaks the premise
Drop(T),
}
In other words, in your case, the compiler can statically determine that your MaybeDrop<NotCopy> has no destructor.
// OK
const _: () = {
use std::mem::needs_drop;
assert!(!needs_drop::<MaybeDrop<NotCopy>>());
};
needs_drop is a bad advise giver here, since NotCopy is a unit struct without a Drop impl and thus indeed dropping it does not matter, not even when wrapped in an enum.
But the point is: my MaybeDrop is considered to need drop by the compiler, because it has a variant contains Box<()> (even I never construct) while yours doesn't have. That's the core why my code doesn't compile. needs_drop is just a tool to show whether the given type has destructor.
Because _VALUE is a const, you know that the compiler has proved that none of its values actually need drop. Therefore, the ManuallyDrop::forget call is not leaky.
If you never use it, that is to say it is never constructed, so it doesn't need drop at all. If you put it in a static field, the lifetime of the field itself is static, so it doesn't need drop too.
The problem in my case is I construct it in const context, then destruct it before taking its field, the destructor must be run.