Code compiles in 1.78.0, but not in 1.79.0

pub static ASSETS_DIR: Option<include_dir::Dir<'_>> = if cfg!(target_arch = "wasm32") {
    Some(include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets"))
} else {
    None
};

This code compiles in older versions of Rust, but after updating to 1.79.0 I get this error:

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:2:10
  |
2 |     Some(include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets"))
  |     -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
  |     |    |
  |     |    creates a temporary value which is freed while still in use
  |     using this value as a static requires that borrow lasts for `'static`
3 | } else {
  | - temporary value is freed at the end of this statement
  |
  = note: this error originates in the macro `include_dir::include_dir` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0716`.

I imagine the issue is related to the if statement because this compiles without any issues in 1.79.0:

pub static ASSETS_DIR: Option<include_dir::Dir<'_>> = Some(include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets"));

This is using include_dir version 0.7.3 and the macro looks something like this when inlined:

    Some(include_dir::Dir::new(
        "",
        &[include_dir::DirEntry::File(include_dir::File::new(
            "test_file.txt",
            b"",
        ))],
    ))

Any help would be greatly appreciated.

This change maybe. Though they didn't seem to think it would break things.

You should definitely file a regression report.

Strangely enough, #121346 looks like it should've made this start successfully compiling, not stop, although AIUI it shouldn't've compiled successfully before nor after the change... outside of a constant initializer context, anyway; IIRC such contexts change how promotion works and are supposed to promote to &'static instead of &'let.

Minimal reproduction:

pub struct Dir<'a>(&'a [File<'a>]);
impl<'a> Dir<'a> {
    pub const fn new(x: &'a [File<'a>]) -> Self {
        Self(x)
    }
}

pub struct File<'a>(&'a str);
impl<'a> File<'a> {
    pub const fn new(x: &'a str) -> Self {
        Self(x)
    }
}

pub static OK: Dir<'static> = Dir::new(&[File::new("")]);
pub static ERR: Dir<'static> = if true {
    Dir::new(&[File::new("")])
} else {
    unreachable!()
};

The workaround for specifically the OP will be to use #[cfg] instead of if cfg!:

#[cfg(target_arch = "wasm32")]
pub static ASSETS_DIR: Option<include_dir::Dir<'_>> = Some(include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets"));
#[cfg(not(target_arch = "wasm32"))]
pub static ASSETS_DIR: Option<include_dir::Dir<'_>> = None;

This is probably what you would prefer anyway, since this means include_dir! won't happen for non-wasm builds, whereas just using if cfg! still expands the include_dir! macro.


Since I went ahead and did the initial look into what's going on, I went ahead and filed a regression issue.

It looks like this is #121557 instead, which deliberately restricts promotion of const fn calls. That also indicates another potential successful rewrite:

pub static ASSETS_DIR: Option<include_dir::Dir<'_>> = {
    let it = include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets");
    if cfg!(target_arch = "wasm32") {
        Some(it)
    } else {
        None
    }
}

The intended rewrite is to change this

    Some(include_dir::Dir::new(
        "",
        &[include_dir::DirEntry::File(include_dir::File::new(
            "test_file.txt",
            b"",
        ))],
    ))

to this

    Some(include_dir::Dir::new(
        "",
        const { &[include_dir::DirEntry::File(include_dir::File::new(
            "test_file.txt",
            b"",
        ))] },
    ))

If you want something to be const-promoted, then it's much better to just tell the compiler explicitly. Having the compiler implicitly promote function calls is problematic for the reasons explained in this RFC.

For this specific case, I'd agree that two separate #[cfg(..)]'d declarations (or perahps cfg_if) is probably better (since it won't even evaluate the include_dir! when cfg'd out).

Note that const { ... } expressions are not available on Rust <= 1.79.0 (and you can't rewrite the output of the include_dir! macro yourself to include one anyway).

If others have a similar issue which is not adequately solved by the #[cfg(..)] solution (e.g. if the conditional is an more complicated expression based on not just a cfg!), a workaround that works on versions 1.78.0 and 1.79.0 is to put the include_dir invocation unconditionally into a separate const item which will be promoted/checked separately, e.g.

pub static ASSETS_DIR: Option<Dir<'static>> = if cfg!(target_arch = "wasm32") {
  const ASSETS_DIR_IMPL: Dir<'static> = include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets");
  Some(ASSETS_DIR_IMPL)
} else {
  None
};

Yeah, that's the backwards-compatible way, but unfortunately it still relies on implicit promotion of File::new -- something rustc should never have accepted and something we might try to phase out eventually, over an edition or so.

I opened an issue against include_dir so hopefully this can be fixed properly in the crate.

Given that the last commit to that repo was 2 years ago, I'm doubtful anything will happen. Looking at open and closed issues I also don't think anything will happen.

The author is active on this forum though, so I'd take the liberty to just ัั @Michael-F-Bryan and hope for the answer from them directly.

Fixed.