Idiomatic way to concatenate `const &str` and also byte string?

Dear Rustacean,

Is it possible to build const &str by concatenatingconst &str?
Here's my code snippet that doesn't work.

const DIR: &str = "/data/tmp/local/test";   // will be used to build other const &str.
const FILE1: &str = "a.txt";    // will be used to build other const &str
const FILE2: &str = "b.txt";
const FILE3: &str = "c.txt";

const PATH1: &str = concat!(DIR, FILE1);   // will be used as Path
const PATH2: &str = concat!(DIR, FILE2);
const PATH3: &str = concat!(DIR, FILE3);

const C_PATH1: &[u8] = concat_bytes!(DIR, FILE1);    // will be used as core::ffi::CStr
const C_PATH2: &[u8] = concat_bytes!(DIR, FILE2);    // will be used as core::ffi::CStr
const C_PATH3: &[u8] = concat_bytes!(DIR, FILE3);    // will be used as core::ffi::CStr

During I write this code, I could find that Rust only have small subset of allowed functions for const and const variables aren't treated as literal.

However, I used to do the similar when I wrote code in C or Java. What would be a ideomatic way to achieve the same? I could achieve the goal at the runtime, but I'm looking for a way to generate those strings/slices at the compile time.

You can use constcat to do some simple stuff. const_format is more powerful but I don't think works with byte slices

constcat seems to work for your example at least

use constcat::{concat, concat_bytes};

const DIR: &str = "/data/tmp/local/test"; // will be used to build other const &str.
const FILE1: &str = "a.txt"; // will be used to build other const &str
const FILE2: &str = "b.txt";
const FILE3: &str = "c.txt";

const PATH1: &str = concat!(DIR, FILE1); // will be used as Path
const PATH2: &str = concat!(DIR, FILE2);
const PATH3: &str = concat!(DIR, FILE3);

const C_PATH1: &[u8] = concat_bytes!(DIR.as_bytes(), FILE1.as_bytes()).as_slice(); // will be used as core::ffi::CStr
const C_PATH2: &[u8] = concat_bytes!(DIR.as_bytes(), FILE2.as_bytes()).as_slice(); // will be used as core::ffi::CStr
const C_PATH3: &[u8] = concat_bytes!(DIR.as_bytes(), FILE3.as_bytes()).as_slice(); // will be used as core::ffi::CStr

fn main() {
    for str in [PATH1, PATH2, PATH3] {
        println!("{}", str)
    }

    for bytes in [C_PATH1, C_PATH2, C_PATH3] {
        println!(
            "{:?}",
            bytes
                .iter()
                .map(|i| char::from_u32(*i as u32).unwrap())
                .collect::<Vec<_>>()
        )
    }
}
3 Likes

Thank you for the crate. The crate has all I wanted, and I learned a lot.

Note that if one is willing to create a value with a 'static lifetime, i.e. a value that isn't cleaned up until program termination, then creating a &'static str can be done at runtime too:

let val: String = format!("The real answer: {}", 42);
let val: Box<str> = val.into_boxed_str();
let val: &'static str = Box::leak(val); // et voilà, a runtime-created &'static str that can contain arbitrary contents. 

Note that in this context the name Box::leak() is a little misleading depending on how the code snippet is used: use it often and that can effectively turn into leaking heap memory. But with some care, it can also be used eg for value interning, or efficiently creating a small pool of values, and just dole out &'static T borrows to those values, which are cheap and Copy, making them pretty ergonomic to use as well.