Const Box<str>::new()

How best to create an empty Box<str> in a const context? This is what I'm currently doing:

#![feature(allocator_api)]
#![feature(const_box)]
#![feature(ptr_internals)]

use std::{alloc::Global, ptr::Unique};

#[inline]
pub const fn box_str_new() -> Box<str> {
    // from Default for Box<str> https://github.com/rust-lang/rust/blob/ca9ff83f1be558d4ce0a9a49b3d4d25034e5bd1e/library/alloc/src/boxed.rs#L1272-L1280
    unsafe {
        let bytes: Unique<[u8]> = Unique::<[u8; 0]>::dangling();
        Box::from_raw_in(bytes.as_ptr() as *mut str, Global)
    }
}

https://play.rust-lang.org/?version=nightly&edition=2021&gist=0e5795358efcd1d62be32e0670b14dac

The features have recently started raising "using it is strongly discouraged" warnings. If there's no better alternative, should <Box<str>>::new() or similar be added? It would align with String::new() and Vec::new() which are both const. Or is this workaround adequate until <Box<str>>::default() is callable in a const context?

You can use NonNull instead of Unique to create the dangling pointer, as documented in the Box memory layout section (paragraph 2).

An alternative could be using the good ol' transmute:

pub const fn box_str_new() -> Box<str> {
    unsafe { std::mem::transmute("") }
}

pub fn main() {
    const B: Box<str> = box_str_new();
    println!("empty string: '{}'", B.as_ref());
}

Quick testing with miri did not flag this as illegal, and this even works on stable.

Transmute is currently only documented as working for Sized types. str is not a sized type, so we are allowed to change the layout of Box<str> to be different from &str. Miri doesn't flag anything regarding layout guarantees. Only if there is currently a layout mismatch.

3 Likes

It would be possible for std to have some sort of const fn new_str() -> Box<str> associated function on Box that doesn't allocate, but I don't know of any tools that std gives you today to safely do this.

Depending on your use case, can you use an Option<Box<str>> instead? You'll be able to initialize that to None at compile time, and it looks like you might already be using empty string as an "uninitialized" state in the code you posted.