How to take ownership of a field without running destructor in const context?

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;

if you are ok with unsafe, you can ptr::read() the a field, then mem::forget() the whole struct.

in const context, you cannot avoid mem::forget() when your type has destructor.

const EXTRACT: MaybeDrop = const {
    let bundled = get();
    let a = unsafe { std::ptr::read(&raw const bundled.a) };
    std::mem::forget(bundled);
    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
};
1 Like

What about this?

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.

Thanks, this solution gives me some inspiration. :smiley:

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.

that's not true at all :

struct LoudDrop<'a>(&'a str);

impl<'a> Drop for LoudDrop<'a> {
    fn drop(&mut self) {
        println!("{} is being dropped !!!!", self.0)
    }
}


const X : LoudDrop<'static> = LoudDrop("bomb");

here X is const but it needs dop

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.