Map on non-copy option in const context

I wanted to achieve the same result as Option::map in a const context. The function I wanted to map is the constructor of a tuple struct. I figured that I could simply "inline" the map call since the "function" I'm mapping is const and match is allowed in const contexts. However, I stumbled upon a strange error which do not really understand.

struct Wrapper<T>(T);

const fn map_wrapper<T>(option: Option<T>) -> Option<Wrapper<T>> {
    match option {
        Some(value) => Some(Wrapper(value)),
        None => None,
    }
}

Here is the error:

error[E0493]: destructor of `Option<T>` cannot be evaluated at compile-time
 --> src/lib.rs:3:25
  |
3 | const fn map_wrapper<T>(option: Option<T>) -> Option<Wrapper<T>> {
  |                         ^^^^^^ the destructor for this type cannot be evaluated in constant functions
...
8 | }
  | - value is dropped here

The problem is that the (not necessarily const) destructor of option is apparently ran. This is what I don't understand since I thought that the match would move option causing drop not to be ran.

Why is option dropped? Is it not moved into the return value? Is it possible to accomplish my goals in stable Rust?

Thanks in advance.

3 Likes

This is the compiler being conservative and requiring the destructor when it is not needed, because the analysis that is exact enough to prove it is not needed isn't quite ready yet.

There are a couple of workarounds available. If you can afford to add the bound T: Copy, that will work, because the compiler knows that Copy types can't have destructors.

const fn map_wrapper<T: Copy>(option: Option<T>) -> Option<Wrapper<T>> {

More awkwardly, you can avoid destructuring or dropping the option value:

const fn map_wrapper<T>(option: Option<T>) -> Option<Wrapper<T>> {
    match option {
        // call unwrap() instead of destructuring
        Some(_) => Some(Wrapper(option.unwrap())),

        // call forget() instead of dropping
        option @ None => {
            mem::forget(option);
            None
        }
    }
}

If you are willing to use nightly Rust, you can add #![feature(const_precise_live_drops)] to ask for the more precise drop analysis, which will accept the function.

Finally and least relevantly, with the unstable const traits features, you can say “this type must be droppable in const” more specifically than T: Copy. (For this particular function, there is no reason to do this, but this is your only option if you actually do need to drop, not forget, a T in a const fn.)

#![feature(const_destruct)]
#![feature(const_trait_impl)]
use std::marker::Destruct;

...

const fn map_wrapper<T>(option: Option<T>) -> Option<Wrapper<T>>
where
    T: [const] Destruct
{
13 Likes