Skip to content

Can't implement a const Option::unwrap because of "destructors cannot be evaluated at compile-time" error. #66753

Closed

Description

@rodrimati1992

It's not possible to implement Option::unwrap as a const fn like:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=8ad0c1cdd371ca0488fab9d872bd6ffe

#![feature(const_fn)]
#![feature(const_if_match)]
#![feature(const_panic)]

const fn unwrap<T>(opt:Option<T>)->T{
    match opt {
        Some(x)=>x,
        None=>panic!("Trying to unwrap a None"),
    }
}

Because the function triggers this error:

error[E0493]: destructors cannot be evaluated at compile-time
 --> src/lib.rs:5:20
  |
5 | const fn unwrap<T>(opt:Option<T>)->T{
  |                    ^^^ constant functions cannot evaluate destructors

error: aborting due to previous error

Activity

  1. ecstatic-morse commented on Nov 25, 2019

    @ecstatic-morse
    Contributor

    This was discussed here in the initial PR for #![feature(const_if_match)]. Basically, we don't clear qualifs from a local (opt in this case) if we move out of the only field of that local. I still think we want to special-case unit structs/enum variants because it will cover commonly used types like Option and Result. Alternatively, we could maybe suppress Drop terminators entirely when a local is moved out of completely, or if this is already implemented, run const checks after this pass occurs.

    cc @rust-lang/wg-const-eval

  2. added
    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Nov 25, 2019
  3. Centril commented on Nov 25, 2019

    @Centril
    Contributor

    This might need to consider #[non_exhaustive] as well similar to how the borrow checker needs to with #66722. cc @matthewjasper

  4. oli-obk commented on Dec 20, 2019

    @oli-obk
    Contributor

    Alternatively, we could maybe suppress Drop terminators entirely when a local is moved out of completely, or if this is already implemented, run const checks after this pass occurs.

    So... we have three situations afaict:

    1. struct without explicit Drop impl, a !Copy field is moved out of (this is the (Vec<T>,) case)
    2. !Copy field of enum variant is moved out of (this is the Some case)
    3. the None case. Even after drop elaboriation, we still execute Drop::drop for the enum in the None case. Well, actually, even after our entire optimization pipeline, we hit bb16 in https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=152c663c007e0c9b67e733bf3d145588 which drops the value.

    So... if even drop elaboration can't figure this out, I'm not sure const qualif should be finding an independent solution.

    Several solutions come to mind:

    1. Introduce a Forget terminator in the spirit of
      • mem::drop == Drop terminator
      • hint::unreachable == Unreachable terminator
      • process::abort == Abort terminator
      • mem::forget == Forget terminator
    2. Add an "early drop elab" pass that detects when you match on an enum and move out in one variant and then automatically treats the other variants as moved out
      • seems dangerous to me, if we generate similar code out of other reasons, the optimization would trigger. Also affects borrowck
    3. Change mir building to not generate drops in the None-like cases
      • Undesirable because we don't want mir building to be complex

    cc @eddyb

  5. eddyb commented on Jan 16, 2020

    @eddyb
    Contributor

    the None case. Even after drop elaboriation, we still execute Drop::drop for the enum in the None case.

    I think there's something missing from drop elaboration's (and other analyses'?) dataflow usage:
    Each edge of a SwitchInt(Discriminant(x)) can assume all other variants of x are uninitialized.

    So the None arm would know Some.0 is uninitialized, and the Some arm moves out of Some.0, so the net effect would be that there's no fields to drop.

    The annoying thing is that we also have to be able to check this in const-checking.


    Hang on... promotion has to run before borrow-checking because it modifies the MIR.
    But const-checking doesn't. Could we run const-checking after borrow-checking?

  6. ecstatic-morse commented on Jan 17, 2020

    @ecstatic-morse
    Contributor

    I'm planning to investigate eddyb's idea early next week (they gave me some instructions to follow over Zulip). This will require a small change to the dataflow analysis API which I'll probably do on top of #65672 just because I'm more familiar with it. Hopefully I can get a proof-of-concept up and running.

  7. added a commit that references this issue on Feb 27, 2020
  8. ecstatic-morse commented on Mar 2, 2020

    @ecstatic-morse
    Contributor

    After #68528 and #68943, the optimized MIR for unwrap no longer has any drop terminators in non-cleanup blocks. In order to make unwrap a const fn, we need to move const-checking (or at least the part of const-checking that looks for live drops) after drop elaboration.

  9. jyn514 commented on May 22, 2020

    @jyn514
    Member

    Silly question but - why does this need compiler support? Can't you mem::forget the option?

  10. rodrimati1992 commented on May 22, 2020

    @rodrimati1992
    ContributorAuthor

    @jyn514

    Can't you mem::forget the option?

    If you mean this:
    https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=69e7f334990002dd979b81e38df3812e

    #![feature(const_forget)]
    #![feature(const_fn)]
    #![feature(const_if_match)]
    #![feature(const_panic)]
    
    const fn unwrap<T>(opt:Option<T>)->T{
        match opt {
            Some(x)=>x,
            None=>{
                std::mem::forget(opt);
                panic!("Trying to unwrap a None")
            },
        }
    }

    It has no effect on the error message:

    error[E0493]: destructors cannot be evaluated at compile-time
     --> src/lib.rs:6:20
      |
    6 | const fn unwrap<T>(opt:Option<T>)->T{
      |                    ^^^ constant functions cannot evaluate destructors
    

    Neither does using ManuallyDrop::new instead.

  11. added 2 commits that reference this issue on Jun 13, 2020
    182ac4b
    1fd1d45
  12. added 2 commits that reference this issue on Jun 15, 2020
    346cf30
    5c61a8d
  13. ecstatic-morse commented on Jun 15, 2020

    @ecstatic-morse
    Contributor

    More precise drop elaboration is now behind #![feature(const_precise_live_drops)], so Option::unwrap could be made unstably const fn with some #[cfg(bootstrap)] magic. We should probably just wait for the next bootstrap bump, however.

  14. added a commit that references this issue on Jun 27, 2020
  15. added a commit that references this issue on Jun 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)A-destructorsArea: Destructors (`Drop`, …)B-unstableBlocker: Implemented in the nightly compiler and unstable.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions